dataiku-sdk 3.1.0 → 3.3.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.
Files changed (149) hide show
  1. package/README.md +253 -38
  2. package/dist/build-metadata.json +1 -1
  3. package/dist/packages/types/src/index.d.ts +102 -0
  4. package/dist/packages/types/src/index.js +76 -0
  5. package/dist/src/auth.js +1 -1
  6. package/dist/src/cli/coerce.js +11 -2
  7. package/dist/src/cli/commands/analysis.js +4 -3
  8. package/dist/src/cli/commands/api-service.js +5 -3
  9. package/dist/src/cli/commands/app.js +61 -18
  10. package/dist/src/cli/commands/bundle.js +3 -2
  11. package/dist/src/cli/commands/code-env.js +23 -16
  12. package/dist/src/cli/commands/connection.d.ts +9 -0
  13. package/dist/src/cli/commands/connection.js +267 -1
  14. package/dist/src/cli/commands/dashboard.js +7 -6
  15. package/dist/src/cli/commands/data-collection.d.ts +2 -0
  16. package/dist/src/cli/commands/data-collection.js +164 -0
  17. package/dist/src/cli/commands/data-quality.js +9 -8
  18. package/dist/src/cli/commands/dataset.js +170 -40
  19. package/dist/src/cli/commands/doctor.js +4 -2
  20. package/dist/src/cli/commands/flow-zone.js +9 -8
  21. package/dist/src/cli/commands/folder.js +9 -8
  22. package/dist/src/cli/commands/future.js +4 -3
  23. package/dist/src/cli/commands/group.d.ts +2 -0
  24. package/dist/src/cli/commands/group.js +109 -0
  25. package/dist/src/cli/commands/index.js +151 -78
  26. package/dist/src/cli/commands/insight.js +8 -7
  27. package/dist/src/cli/commands/job.js +4 -3
  28. package/dist/src/cli/commands/knowledge-bank.d.ts +2 -0
  29. package/dist/src/cli/commands/knowledge-bank.js +80 -0
  30. package/dist/src/cli/commands/llm.d.ts +2 -0
  31. package/dist/src/cli/commands/llm.js +93 -0
  32. package/dist/src/cli/commands/macro.d.ts +2 -0
  33. package/dist/src/cli/commands/macro.js +221 -0
  34. package/dist/src/cli/commands/meaning.js +5 -4
  35. package/dist/src/cli/commands/ml-task.js +21 -8
  36. package/dist/src/cli/commands/model-evaluation-store.js +4 -3
  37. package/dist/src/cli/commands/notebook.js +17 -12
  38. package/dist/src/cli/commands/plugin.d.ts +2 -0
  39. package/dist/src/cli/commands/plugin.js +799 -0
  40. package/dist/src/cli/commands/project-folder.d.ts +2 -0
  41. package/dist/src/cli/commands/project-folder.js +197 -0
  42. package/dist/src/cli/commands/project-git.js +14 -54
  43. package/dist/src/cli/commands/project-library.js +7 -6
  44. package/dist/src/cli/commands/project.js +55 -8
  45. package/dist/src/cli/commands/recipe.js +50 -14
  46. package/dist/src/cli/commands/saved-model.d.ts +5 -0
  47. package/dist/src/cli/commands/saved-model.js +392 -4
  48. package/dist/src/cli/commands/scenario.js +147 -9
  49. package/dist/src/cli/commands/sql.js +3 -2
  50. package/dist/src/cli/commands/statistics.js +7 -6
  51. package/dist/src/cli/commands/user.d.ts +8 -0
  52. package/dist/src/cli/commands/user.js +296 -0
  53. package/dist/src/cli/commands/variable.js +2 -1
  54. package/dist/src/cli/commands/webapp.js +2 -1
  55. package/dist/src/cli/commands/wiki.js +7 -6
  56. package/dist/src/cli/contract.d.ts +2 -3
  57. package/dist/src/cli/contract.js +1167 -163
  58. package/dist/src/cli/flags.d.ts +4 -0
  59. package/dist/src/cli/flags.js +50 -2
  60. package/dist/src/cli/helpers/app-successor.d.ts +19 -4
  61. package/dist/src/cli/helpers/app-successor.js +90 -30
  62. package/dist/src/cli/helpers/cleanup.js +3 -1
  63. package/dist/src/cli/helpers/flow-zone.js +1 -2
  64. package/dist/src/cli/output.d.ts +1 -1
  65. package/dist/src/cli/output.js +23 -10
  66. package/dist/src/cli/usage.d.ts +1 -0
  67. package/dist/src/cli/usage.js +47 -1
  68. package/dist/src/cli.js +225 -95
  69. package/dist/src/client.d.ts +86 -6
  70. package/dist/src/client.js +263 -50
  71. package/dist/src/errors.d.ts +3 -0
  72. package/dist/src/errors.js +4 -0
  73. package/dist/src/index.d.ts +16 -7
  74. package/dist/src/index.js +11 -2
  75. package/dist/src/resources/analyses.js +1 -8
  76. package/dist/src/resources/applications.d.ts +8 -6
  77. package/dist/src/resources/applications.js +21 -21
  78. package/dist/src/resources/base.d.ts +4 -0
  79. package/dist/src/resources/base.js +15 -0
  80. package/dist/src/resources/bundles.d.ts +9 -4
  81. package/dist/src/resources/bundles.js +10 -5
  82. package/dist/src/resources/connections.d.ts +78 -1
  83. package/dist/src/resources/connections.js +132 -1
  84. package/dist/src/resources/data-collections.d.ts +118 -0
  85. package/dist/src/resources/data-collections.js +49 -0
  86. package/dist/src/resources/datasets.d.ts +69 -0
  87. package/dist/src/resources/datasets.js +99 -1
  88. package/dist/src/resources/folders.js +2 -1
  89. package/dist/src/resources/futures.d.ts +4 -1
  90. package/dist/src/resources/futures.js +38 -6
  91. package/dist/src/resources/groups.d.ts +47 -0
  92. package/dist/src/resources/groups.js +56 -0
  93. package/dist/src/resources/jobs.d.ts +8 -13
  94. package/dist/src/resources/jobs.js +41 -16
  95. package/dist/src/resources/knowledge-banks.d.ts +58 -0
  96. package/dist/src/resources/knowledge-banks.js +61 -0
  97. package/dist/src/resources/llms.d.ts +92 -0
  98. package/dist/src/resources/llms.js +62 -0
  99. package/dist/src/resources/macros.d.ts +121 -0
  100. package/dist/src/resources/macros.js +231 -0
  101. package/dist/src/resources/ml-tasks.d.ts +37 -2
  102. package/dist/src/resources/ml-tasks.js +100 -20
  103. package/dist/src/resources/model-evaluation-stores.js +1 -8
  104. package/dist/src/resources/notebooks.d.ts +43 -7
  105. package/dist/src/resources/notebooks.js +53 -13
  106. package/dist/src/resources/plugins.d.ts +265 -0
  107. package/dist/src/resources/plugins.js +458 -0
  108. package/dist/src/resources/project-folders.d.ts +71 -0
  109. package/dist/src/resources/project-folders.js +57 -0
  110. package/dist/src/resources/project-git.d.ts +6 -0
  111. package/dist/src/resources/project-git.js +69 -42
  112. package/dist/src/resources/project-library.d.ts +0 -1
  113. package/dist/src/resources/project-library.js +1 -17
  114. package/dist/src/resources/projects.d.ts +19 -3
  115. package/dist/src/resources/projects.js +30 -18
  116. package/dist/src/resources/recipes.d.ts +15 -1
  117. package/dist/src/resources/recipes.js +73 -6
  118. package/dist/src/resources/saved-models.d.ts +134 -1
  119. package/dist/src/resources/saved-models.js +214 -6
  120. package/dist/src/resources/scenarios.d.ts +105 -0
  121. package/dist/src/resources/scenarios.js +232 -106
  122. package/dist/src/resources/sql.js +5 -1
  123. package/dist/src/resources/users.d.ts +129 -0
  124. package/dist/src/resources/users.js +128 -0
  125. package/dist/src/resources/webapps.d.ts +15 -2
  126. package/dist/src/resources/webapps.js +18 -4
  127. package/dist/src/schemas.d.ts +2 -2
  128. package/dist/src/schemas.js +1 -1
  129. package/dist/src/skill.d.ts +13 -4
  130. package/dist/src/skill.js +75 -24
  131. package/dist/src/utils/deep-merge.d.ts +1 -1
  132. package/dist/src/utils/polling.d.ts +20 -0
  133. package/dist/src/utils/polling.js +27 -0
  134. package/dist/src/utils/response-file.d.ts +2 -0
  135. package/dist/src/utils/response-file.js +34 -0
  136. package/dist/src/utils/secret-sanitize.d.ts +54 -0
  137. package/dist/src/utils/secret-sanitize.js +101 -0
  138. package/package.json +8 -5
  139. package/packages/types/dist/index.d.ts +102 -0
  140. package/packages/types/dist/index.js +76 -0
  141. package/plugin.json +1 -1
  142. package/skills/dataiku-dss/SKILL.md +47 -225
  143. package/skills/dataiku-dss/references/app-releases.md +28 -0
  144. package/skills/dataiku-dss/references/authentication.md +39 -0
  145. package/skills/dataiku-dss/references/coding.md +8 -0
  146. package/skills/dataiku-dss/references/discovery.md +30 -0
  147. package/skills/dataiku-dss/references/flow-maps.md +12 -0
  148. package/skills/dataiku-dss/references/mutations.md +33 -0
  149. package/skills/dataiku-dss/references/troubleshooting.md +29 -0
@@ -1,5 +1,13 @@
1
+ import { writeResponseToFile, } from "../../utils/response-file.js";
2
+ import { json, jsonInput, parseBooleanOption, requiredJsonInput, } from "../coerce.js";
3
+ import { executionMode, } from "../flags.js";
1
4
  import { readIfExists, skipResult, } from "../output.js";
2
- import { requireArgs, } from "../usage.js";
5
+ import { requireArgs, UsageError, } from "../usage.js";
6
+ /**
7
+ * Mutating actions accept --plan (buildMutationPlan in contract.ts owns the
8
+ * --plan path centrally); the handlers below keep --dry-run support with a
9
+ * zero-request planned payload, mirroring set-active/delete.
10
+ */
3
11
  export const savedModelCommands = {
4
12
  list: {
5
13
  handler: (c, _a, f) => c.savedModels.list(f["project-key"]),
@@ -7,6 +15,64 @@ export const savedModelCommands = {
7
15
  description: "List saved models in a project.",
8
16
  examples: ["dss saved-model list --project-key PROJECT",],
9
17
  },
18
+ "create-external": {
19
+ handler: async (c, a, f) => {
20
+ const usage = "dss saved-model create-external <name> --type MLFLOW_PYFUNC|PROXY_MODEL [--prediction-type TYPE] [--configuration JSON] [--data JSON] [--dry-run] [--project-key KEY]";
21
+ requireArgs(a, 1, usage);
22
+ const savedModelType = f["type"]?.trim();
23
+ if (savedModelType !== "MLFLOW_PYFUNC" && savedModelType !== "PROXY_MODEL") {
24
+ throw new UsageError("--type must be MLFLOW_PYFUNC or PROXY_MODEL.", "invalid_enum");
25
+ }
26
+ const predictionTypeRaw = f["prediction-type"];
27
+ const predictionType = predictionTypeRaw === undefined || predictionTypeRaw === ""
28
+ ? undefined
29
+ : predictionTypeRaw.trim();
30
+ if (predictionType !== undefined
31
+ && predictionType !== "BINARY_CLASSIFICATION"
32
+ && predictionType !== "MULTICLASS"
33
+ && predictionType !== "REGRESSION") {
34
+ throw new UsageError("--prediction-type must be BINARY_CLASSIFICATION, MULTICLASS, or REGRESSION.", "invalid_enum");
35
+ }
36
+ const configuration = f["configuration"] !== undefined
37
+ ? requiredJsonInput(f, "--configuration must be a JSON object (--data/--data-file/--stdin).")
38
+ : jsonInput(f);
39
+ if (savedModelType === "PROXY_MODEL" && configuration === undefined) {
40
+ throw new UsageError('--configuration JSON is required for PROXY_MODEL (e.g. {"protocol":"sagemaker","region":"eu-west-1"}).', "missing_required_flag");
41
+ }
42
+ const projectKey = f["project-key"];
43
+ if (executionMode(f).dryRun) {
44
+ return {
45
+ dryRun: true,
46
+ action: "create-external",
47
+ resource: "saved-model",
48
+ id: a[0],
49
+ projectKey,
50
+ payload: {
51
+ savedModelType,
52
+ name: a[0],
53
+ ...(predictionType !== undefined ? { predictionType, } : {}),
54
+ ...(configuration !== undefined
55
+ ? { proxyModelConfiguration: configuration, }
56
+ : {}),
57
+ },
58
+ };
59
+ }
60
+ return c.savedModels.createExternal({
61
+ savedModelType,
62
+ name: a[0],
63
+ ...(predictionType !== undefined ? { predictionType, } : {}),
64
+ ...(configuration !== undefined
65
+ ? { proxyModelConfiguration: configuration, }
66
+ : {}),
67
+ }, projectKey);
68
+ },
69
+ usage: "dss saved-model create-external <name> --type MLFLOW_PYFUNC|PROXY_MODEL [--prediction-type TYPE] [--configuration JSON] [--data JSON] [--dry-run] [--project-key KEY]",
70
+ description: "Create a saved model for MLflow or external (proxy) models. --type selects savedModelType; --prediction-type is BINARY_CLASSIFICATION, MULTICLASS, or REGRESSION; --configuration (JSON via --data/--data-file/--stdin) is the PROXY_MODEL protocol configuration forwarded as proxyModelConfiguration.",
71
+ examples: [
72
+ "dss saved-model create-external 'MLflow Demo' --type MLFLOW_PYFUNC --prediction-type BINARY_CLASSIFICATION",
73
+ 'dss saved-model create-external \'SageMaker Model\' --type PROXY_MODEL --prediction-type BINARY_CLASSIFICATION --data \'{"protocol":"sagemaker","region":"eu-west-1"}\'',
74
+ ],
75
+ },
10
76
  get: {
11
77
  handler: (c, a, f) => {
12
78
  requireArgs(a, 1, "dss saved-model get <modelId> [--project-key KEY]");
@@ -16,6 +82,33 @@ export const savedModelCommands = {
16
82
  description: "Get a saved model.",
17
83
  examples: ["dss saved-model get MODEL_ID --project-key PROJECT",],
18
84
  },
85
+ "update-settings": {
86
+ handler: async (c, a, f) => {
87
+ const usage = "dss saved-model update-settings <modelId> (--data JSON|--data-file PATH|--stdin) [--dry-run] [--project-key KEY]";
88
+ requireArgs(a, 1, usage);
89
+ const settings = requiredJsonInput(f, "Saved-model settings JSON is required via --data, --data-file, or --stdin (GET-then-PUT the full settings document).");
90
+ const projectKey = f["project-key"];
91
+ if (executionMode(f).dryRun) {
92
+ return {
93
+ dryRun: true,
94
+ action: "update-settings",
95
+ resource: "saved-model",
96
+ id: a[0],
97
+ projectKey,
98
+ payload: settings,
99
+ };
100
+ }
101
+ return c.savedModels.updateSettings(a[0], settings, projectKey).then(() => ({
102
+ updated: a[0],
103
+ resource: "saved-model",
104
+ }));
105
+ },
106
+ usage: "dss saved-model update-settings <modelId> (--data JSON|--data-file PATH|--stdin) [--dry-run] [--project-key KEY]",
107
+ description: "Save the settings of a saved model. The payload must be the complete settings document as returned by `saved-model get` (GET-then-PUT workflow, per the official client).",
108
+ examples: [
109
+ "dss saved-model get MODEL_ID --project-key P | jq . > settings.json && dss saved-model update-settings MODEL_ID --data-file settings.json --project-key P",
110
+ ],
111
+ },
19
112
  "list-versions": {
20
113
  handler: (c, a, f) => {
21
114
  requireArgs(a, 1, "dss saved-model list-versions <modelId> [--project-key KEY]");
@@ -36,11 +129,22 @@ export const savedModelCommands = {
36
129
  "dss saved-model version-details MODEL_ID VERSION_ID --project-key PROJECT",
37
130
  ],
38
131
  },
132
+ "version-snippet": {
133
+ handler: (c, a, f) => {
134
+ requireArgs(a, 2, "dss saved-model version-snippet <modelId> <versionId> [--project-key KEY]");
135
+ return c.savedModels.versionSnippet(a[0], a[1], f["project-key"]);
136
+ },
137
+ usage: "dss saved-model version-snippet <modelId> <versionId> [--project-key KEY]",
138
+ description: "Get the snippet (short summary) for one saved-model version.",
139
+ examples: [
140
+ "dss saved-model version-snippet MODEL_ID VERSION_ID --project-key PROJECT",
141
+ ],
142
+ },
39
143
  "set-active": {
40
144
  handler: async (c, a, f) => {
41
145
  requireArgs(a, 2, "dss saved-model set-active <modelId> <versionId> [--dry-run] [--project-key KEY]");
42
146
  const projectKey = f["project-key"];
43
- if (f["dry-run"] === true) {
147
+ if (executionMode(f).dryRun) {
44
148
  return {
45
149
  dryRun: true,
46
150
  action: "set-active",
@@ -60,15 +164,46 @@ export const savedModelCommands = {
60
164
  "dss saved-model set-active MODEL_ID VERSION_ID --dry-run --project-key PROJECT",
61
165
  ],
62
166
  },
167
+ "delete-versions": {
168
+ handler: async (c, a, f) => {
169
+ const usage = "dss saved-model delete-versions <modelId> <versionsCSV> [--remove-intermediate true|false] [--dry-run] [--project-key KEY]";
170
+ requireArgs(a, 2, usage);
171
+ const versions = a[1].split(",").map((v) => v.trim()).filter((v) => v.length > 0);
172
+ if (versions.length === 0) {
173
+ throw new UsageError("<versionsCSV> must contain at least one version id.", "validation_failed");
174
+ }
175
+ const removeIntermediate = parseBooleanOption(f["remove-intermediate"], "--remove-intermediate")
176
+ ?? true;
177
+ const projectKey = f["project-key"];
178
+ if (executionMode(f).dryRun) {
179
+ return {
180
+ dryRun: true,
181
+ action: "delete-versions",
182
+ resource: "saved-model",
183
+ id: a[0],
184
+ projectKey,
185
+ payload: { versions, removeIntermediate, },
186
+ };
187
+ }
188
+ await c.savedModels.deleteVersions(versions, { removeIntermediate, }, a[0], projectKey);
189
+ return { deletedVersions: versions, resource: "saved-model", id: a[0], };
190
+ },
191
+ usage: "dss saved-model delete-versions <modelId> <versionsCSV> [--remove-intermediate true|false] [--dry-run] [--project-key KEY]",
192
+ description: "Delete one or more versions of a saved model (comma-separated version ids). Destructive. Implemented as POST .../actions/delete-versions per the official Python client (the REST reference's GET entry is a doc bug); the transport never retries this request.",
193
+ examples: [
194
+ "dss saved-model delete-versions MODEL_ID VERSION_A,VERSION_B --project-key PROJECT",
195
+ "dss saved-model delete-versions MODEL_ID VERSION_A --remove-intermediate false --dry-run --project-key PROJECT",
196
+ ],
197
+ },
63
198
  delete: {
64
199
  handler: async (c, a, f) => {
65
200
  requireArgs(a, 1, "dss saved-model delete <modelId> [--if-exists] [--dry-run] [--project-key KEY]");
66
201
  const projectKey = f["project-key"];
67
- if (f["dry-run"] === true || f["if-exists"] === true) {
202
+ if (executionMode(f).dryRun || f["if-exists"] === true) {
68
203
  const current = await readIfExists(() => c.savedModels.get(a[0], projectKey));
69
204
  if (!current)
70
205
  return skipResult("saved-model", a[0], "missing");
71
- if (f["dry-run"] === true) {
206
+ if (executionMode(f).dryRun) {
72
207
  return { dryRun: true, action: "delete", resource: "saved-model", id: a[0], current, };
73
208
  }
74
209
  }
@@ -82,4 +217,257 @@ export const savedModelCommands = {
82
217
  "dss saved-model delete MODEL_ID --dry-run --project-key PROJECT",
83
218
  ],
84
219
  },
220
+ "import-mlflow-version": {
221
+ handler: async (c, a, f) => {
222
+ const usage = "dss saved-model import-mlflow-version <modelId> <versionId> --archive PATH [--code-env NAME] [--container-exec-config NAME] [--set-active true|false] [--binary-classification-threshold N] [--dry-run] [--project-key KEY]";
223
+ requireArgs(a, 2, usage);
224
+ const archive = f["archive"];
225
+ if (!archive)
226
+ throw new UsageError("--archive PATH is required.", "missing_required_flag");
227
+ const projectKey = f["project-key"];
228
+ const options = mlflowImportOptions(f);
229
+ if (executionMode(f).dryRun) {
230
+ return {
231
+ dryRun: true,
232
+ action: "import-mlflow-version",
233
+ resource: "saved-model",
234
+ id: a[0],
235
+ versionId: a[1],
236
+ projectKey,
237
+ payload: { ...options, source: { kind: "local-archive", archive, }, },
238
+ };
239
+ }
240
+ return c.savedModels.importMlflowVersion(archive, a[1], options, a[0], projectKey);
241
+ },
242
+ usage: "dss saved-model import-mlflow-version <modelId> <versionId> --archive PATH [--code-env NAME] [--container-exec-config NAME] [--set-active true|false] [--binary-classification-threshold N] [--dry-run] [--project-key KEY]",
243
+ description: "Import a new MLflow version from a local zip archive of the MLflow model folder (multipart upload). The saved model must be of type MLFLOW_PYFUNC.",
244
+ examples: [
245
+ "dss saved-model import-mlflow-version MODEL_ID v1 --archive ./mlflow-model.zip --project-key PROJECT",
246
+ "dss saved-model import-mlflow-version MODEL_ID v1 --archive ./model.zip --code-env INHERIT --set-active false --project-key PROJECT",
247
+ ],
248
+ },
249
+ "import-mlflow-version-from-folder": {
250
+ handler: async (c, a, f) => {
251
+ const usage = "dss saved-model import-mlflow-version-from-folder <modelId> <versionId> --folder PROJECT.FOLDER_ID --path PATH [--code-env NAME] [--container-exec-config NAME] [--set-active true|false] [--binary-classification-threshold N] [--dry-run] [--project-key KEY]";
252
+ requireArgs(a, 2, usage);
253
+ const folder = f["folder"];
254
+ if (!folder) {
255
+ throw new UsageError("--folder PROJECT.FOLDER_ID is required.", "missing_required_flag");
256
+ }
257
+ const path = f["path"];
258
+ if (!path)
259
+ throw new UsageError("--path PATH is required.", "missing_required_flag");
260
+ const projectKey = f["project-key"];
261
+ const options = mlflowImportOptions(f);
262
+ if (executionMode(f).dryRun) {
263
+ return {
264
+ dryRun: true,
265
+ action: "import-mlflow-version-from-folder",
266
+ resource: "saved-model",
267
+ id: a[0],
268
+ versionId: a[1],
269
+ projectKey,
270
+ payload: { ...options, source: { kind: "managed-folder", folderRef: folder, path, }, },
271
+ };
272
+ }
273
+ return c.savedModels.importMlflowVersionFromFolder(folder, path, a[1], options, a[0], projectKey);
274
+ },
275
+ usage: "dss saved-model import-mlflow-version-from-folder <modelId> <versionId> --folder PROJECT.FOLDER_ID --path PATH [--code-env NAME] [--container-exec-config NAME] [--set-active true|false] [--binary-classification-threshold N] [--dry-run] [--project-key KEY]",
276
+ description: "Import a new MLflow version from an MLflow model folder stored in a managed folder (folderRef PROJECT_KEY.FOLDER_ID). Sends the backend-mandated multipart request with a zero-byte file part, matching the official Python client.",
277
+ examples: [
278
+ "dss saved-model import-mlflow-version-from-folder MODEL_ID v1 --folder PROJ.z7pSyYzE --path /artifacts/model --project-key PROJ",
279
+ ],
280
+ },
281
+ "external-metadata-get": {
282
+ handler: (c, a, f) => {
283
+ requireArgs(a, 2, "dss saved-model external-metadata-get <modelId> <versionId> [--project-key KEY]");
284
+ return c.savedModels.externalMetadataGet(a[0], a[1], f["project-key"]);
285
+ },
286
+ usage: "dss saved-model external-metadata-get <modelId> <versionId> [--project-key KEY]",
287
+ description: "Get the external-ml metadata of a saved-model version (MLflow or proxy).",
288
+ examples: [
289
+ "dss saved-model external-metadata-get MODEL_ID VERSION_ID --project-key PROJECT",
290
+ ],
291
+ },
292
+ "external-metadata-put": {
293
+ handler: async (c, a, f) => {
294
+ const usage = "dss saved-model external-metadata-put <modelId> <versionId> (--data JSON|--data-file PATH|--stdin) [--container-exec-config NAME] [--dry-run] [--project-key KEY]";
295
+ requireArgs(a, 2, usage);
296
+ const metadata = requiredJsonInput(f, "Metadata JSON is required via --data, --data-file, or --stdin (GET-then-PUT the full external-ml metadata document).");
297
+ const projectKey = f["project-key"];
298
+ const containerExecConfigName = f["container-exec-config"];
299
+ if (executionMode(f).dryRun) {
300
+ return {
301
+ dryRun: true,
302
+ action: "external-metadata-put",
303
+ resource: "saved-model",
304
+ id: a[0],
305
+ versionId: a[1],
306
+ projectKey,
307
+ payload: {
308
+ metadata,
309
+ ...(containerExecConfigName !== undefined ? { containerExecConfigName, } : {}),
310
+ },
311
+ };
312
+ }
313
+ return c.savedModels.externalMetadataPut(a[0], a[1], metadata, containerExecConfigName === undefined ? undefined : { containerExecConfigName, }, projectKey).then(() => ({ updated: a[0], versionId: a[1], resource: "saved-model", }));
314
+ },
315
+ usage: "dss saved-model external-metadata-put <modelId> <versionId> (--data JSON|--data-file PATH|--stdin) [--container-exec-config NAME] [--dry-run] [--project-key KEY]",
316
+ description: "Save the external-ml metadata of a saved-model version. The payload must be the full metadata document as returned by external-metadata-get (GET-then-PUT). DSS requires the containerExecConfigName query parameter; it defaults to NONE for an external API caller (LOCAL-CONFIG resolution) when --container-exec-config is omitted.",
317
+ examples: [
318
+ "dss saved-model external-metadata-put MODEL_ID VERSION_ID --data-file metadata.json --project-key PROJECT",
319
+ ],
320
+ },
321
+ "evaluate-version": {
322
+ handler: async (c, a, f) => {
323
+ const usage = "dss saved-model evaluate-version <modelId> <versionId> --dataset REF [--container-exec-config NAME] [--sampling JSON] [--use-optimal-threshold true|false] [--skip-expensive-reports true|false] [--dry-run] [--project-key KEY]";
324
+ requireArgs(a, 2, usage);
325
+ const dataset = f["dataset"];
326
+ if (!dataset)
327
+ throw new UsageError("--dataset REF is required.", "missing_required_flag");
328
+ const projectKey = f["project-key"];
329
+ // --sampling carries its own JSON value: parse the flag itself (the
330
+ // old requiredJsonInput read --data/--stdin, so a valid JSON flag
331
+ // was rejected as missing).
332
+ const sampling = json(f["sampling"], "--sampling");
333
+ // Official external-caller semantics (DSS 15): the evaluation
334
+ // container exec resolves as LOCAL-CONFIG -> NONE, so the body
335
+ // always carries an explicit containerExecConfigName defaulting to
336
+ // NONE; --container-exec-config still passes an explicit value
337
+ // (including INHERIT) through unchanged.
338
+ const containerExecConfigName = f["container-exec-config"] ?? "NONE";
339
+ const body = {
340
+ datasetRef: dataset,
341
+ containerExecConfigName,
342
+ ...(sampling !== undefined ? { samplingParam: sampling, } : {}),
343
+ };
344
+ const options = {
345
+ ...(f["use-optimal-threshold"] !== undefined
346
+ ? {
347
+ useOptimalThreshold: parseBooleanOption(f["use-optimal-threshold"], "--use-optimal-threshold"),
348
+ }
349
+ : {}),
350
+ ...(f["skip-expensive-reports"] !== undefined
351
+ ? {
352
+ skipExpensiveReports: parseBooleanOption(f["skip-expensive-reports"], "--skip-expensive-reports"),
353
+ }
354
+ : {}),
355
+ };
356
+ if (executionMode(f).dryRun) {
357
+ return {
358
+ dryRun: true,
359
+ action: "evaluate-version",
360
+ resource: "saved-model",
361
+ id: a[0],
362
+ versionId: a[1],
363
+ projectKey,
364
+ payload: { ...body, ...options, },
365
+ };
366
+ }
367
+ await c.savedModels.evaluateVersion(body, options, a[0], a[1], projectKey);
368
+ return { evaluated: a[0], versionId: a[1], resource: "saved-model", };
369
+ },
370
+ usage: "dss saved-model evaluate-version <modelId> <versionId> --dataset REF [--container-exec-config NAME] [--sampling JSON] [--use-optimal-threshold true|false] [--skip-expensive-reports true|false] [--dry-run] [--project-key KEY]",
371
+ description: 'Evaluate an external/MLflow model version on a dataset. external-metadata-put must have been called first. --sampling is an optional JSON sampling parameter (e.g. {"samplingMethod":"HEAD_SEQUENTIAL","maxRecords":100}).',
372
+ examples: [
373
+ "dss saved-model evaluate-version MODEL_ID VERSION_ID --dataset PROJECT.my-dataset --project-key PROJECT",
374
+ "dss saved-model evaluate-version MODEL_ID VERSION_ID --dataset ds --skip-expensive-reports true --project-key PROJECT",
375
+ ],
376
+ },
377
+ "download-scoring-jar": {
378
+ handler: async (c, a, f) => {
379
+ const usage = "dss saved-model download-scoring-jar <modelId> <versionId> --output PATH [--full-class-name NAME] [--include-libs true|false] [--project-key KEY]";
380
+ requireArgs(a, 2, usage);
381
+ const out = f["output"];
382
+ if (!out)
383
+ throw new UsageError("--output PATH is required.", "missing_required_flag");
384
+ const fullClassName = f["full-class-name"];
385
+ const jarOptions = {
386
+ ...(fullClassName !== undefined ? { fullClassName, } : {}),
387
+ ...(f["include-libs"] !== undefined
388
+ ? { includeLibs: parseBooleanOption(f["include-libs"], "--include-libs"), }
389
+ : {}),
390
+ };
391
+ const res = await c.savedModels.downloadScoringJar(jarOptions, a[0], a[1], f["project-key"]);
392
+ if (!res.body) {
393
+ throw new Error("savedModels.downloadScoringJar response did not include a body");
394
+ }
395
+ const bytes = await writeResponseToFile(out, res);
396
+ return { path: out, bytes, };
397
+ },
398
+ usage: "dss saved-model download-scoring-jar <modelId> <versionId> --output PATH [--full-class-name NAME] [--include-libs true|false] [--project-key KEY]",
399
+ description: "Download the optimized scoring JAR of a version (license-gated server side). --full-class-name forwards the documented fullClassName query parameter; --include-libs toggles including scoring libraries.",
400
+ examples: [
401
+ "dss saved-model download-scoring-jar MODEL_ID VERSION_ID --output ./scoring.jar --project-key PROJECT",
402
+ "dss saved-model download-scoring-jar MODEL_ID VERSION_ID --output ./scoring.jar --full-class-name model.Model --include-libs false --project-key PROJECT",
403
+ ],
404
+ },
405
+ "download-scoring-pmml": {
406
+ handler: async (c, a, f) => {
407
+ requireArgs(a, 2, "dss saved-model download-scoring-pmml <modelId> <versionId> --output PATH [--project-key KEY]");
408
+ const out = f["output"];
409
+ if (!out)
410
+ throw new UsageError("--output PATH is required.", "missing_required_flag");
411
+ const res = await c.savedModels.downloadScoringPmml(a[0], a[1], f["project-key"]);
412
+ if (!res.body) {
413
+ throw new Error("savedModels.downloadScoringPmml response did not include a body");
414
+ }
415
+ const bytes = await writeResponseToFile(out, res);
416
+ return { path: out, bytes, };
417
+ },
418
+ usage: "dss saved-model download-scoring-pmml <modelId> <versionId> --output PATH [--project-key KEY]",
419
+ description: "Download the PMML scoring file of a version (license-gated server side).",
420
+ examples: [
421
+ "dss saved-model download-scoring-pmml MODEL_ID VERSION_ID --output ./model.pmml --project-key PROJECT",
422
+ ],
423
+ },
424
+ "set-user-meta": {
425
+ handler: async (c, a, f) => {
426
+ const usage = "dss saved-model set-user-meta <modelId> <versionId> (--data JSON|--data-file PATH|--stdin) [--dry-run] [--project-key KEY]";
427
+ requireArgs(a, 2, usage);
428
+ const userMeta = requiredJsonInput(f, "User-meta JSON is required via --data, --data-file, or --stdin.");
429
+ const projectKey = f["project-key"];
430
+ if (executionMode(f).dryRun) {
431
+ return {
432
+ dryRun: true,
433
+ action: "set-user-meta",
434
+ resource: "saved-model",
435
+ id: a[0],
436
+ versionId: a[1],
437
+ projectKey,
438
+ payload: userMeta,
439
+ };
440
+ }
441
+ return c.savedModels.setUserMeta(a[0], a[1], userMeta, projectKey).then(() => ({
442
+ updated: a[0],
443
+ versionId: a[1],
444
+ resource: "saved-model",
445
+ }));
446
+ },
447
+ usage: "dss saved-model set-user-meta <modelId> <versionId> (--data JSON|--data-file PATH|--stdin) [--dry-run] [--project-key KEY]",
448
+ description: 'Update the user metadata of a model version. Send only the "userMeta" field of a previously-retrieved version-details object.',
449
+ examples: [
450
+ 'dss saved-model set-user-meta MODEL_ID VERSION_ID --data \'{"name":"Churn model","description":"v2"}\' --project-key PROJECT',
451
+ ],
452
+ },
85
453
  };
454
+ function mlflowImportOptions(f) {
455
+ const codeEnvName = f["code-env"];
456
+ const containerExecConfigName = f["container-exec-config"];
457
+ const setActive = f["set-active"] !== undefined
458
+ ? parseBooleanOption(f["set-active"], "--set-active")
459
+ : undefined;
460
+ const thresholdRaw = f["binary-classification-threshold"];
461
+ const binaryClassificationThreshold = thresholdRaw === undefined
462
+ ? undefined
463
+ : Number(thresholdRaw);
464
+ if (binaryClassificationThreshold !== undefined && !Number.isFinite(binaryClassificationThreshold)) {
465
+ throw new UsageError("--binary-classification-threshold must be a number.", "invalid_flag_value");
466
+ }
467
+ return {
468
+ ...(codeEnvName !== undefined ? { codeEnvName, } : {}),
469
+ ...(containerExecConfigName !== undefined ? { containerExecConfigName, } : {}),
470
+ ...(setActive !== undefined ? { setActive, } : {}),
471
+ ...(binaryClassificationThreshold !== undefined ? { binaryClassificationThreshold, } : {}),
472
+ };
473
+ }
@@ -1,5 +1,6 @@
1
1
  import { scenarioUpdatePreview, } from "../../resources/scenarios.js";
2
- import { jsonInput, num, } from "../coerce.js";
2
+ import { jsonInput, num, parseBooleanOption, } from "../coerce.js";
3
+ import { executionMode, } from "../flags.js";
3
4
  import { encodedProjectEndpoint, readIfExists, skipResult, } from "../output.js";
4
5
  import { requireArgs, UsageError, } from "../usage.js";
5
6
  export const scenarioCommands = {
@@ -26,7 +27,7 @@ export const scenarioCommands = {
26
27
  pollIntervalMs: num(f["poll-interval"], "--poll-interval"),
27
28
  timeoutMs: num(f["timeout"], "--timeout"),
28
29
  };
29
- if (f["dry-run"] === true) {
30
+ if (executionMode(f).dryRun) {
30
31
  return {
31
32
  dryRun: true,
32
33
  action: "run",
@@ -54,7 +55,7 @@ export const scenarioCommands = {
54
55
  pollIntervalMs: num(f["poll-interval"], "--poll-interval"),
55
56
  timeoutMs: num(f["timeout"], "--timeout"),
56
57
  };
57
- if (f["dry-run"] === true) {
58
+ if (executionMode(f).dryRun) {
58
59
  return {
59
60
  dryRun: true,
60
61
  action: "run-and-wait",
@@ -87,11 +88,11 @@ export const scenarioCommands = {
87
88
  handler: async (c, a, f) => {
88
89
  requireArgs(a, 1, "dss scenario delete <id>");
89
90
  const pk = f["project-key"];
90
- if (f["dry-run"] === true || f["if-exists"] === true) {
91
+ if (executionMode(f).dryRun || f["if-exists"] === true) {
91
92
  const current = await readIfExists(() => c.scenarios.get(a[0], { projectKey: pk, }));
92
93
  if (!current)
93
94
  return skipResult("scenario", a[0], "missing");
94
- if (f["dry-run"] === true) {
95
+ if (executionMode(f).dryRun) {
95
96
  return { dryRun: true, action: "delete", resource: "scenario", id: a[0], current, };
96
97
  }
97
98
  }
@@ -112,13 +113,13 @@ export const scenarioCommands = {
112
113
  scenarioType: f["type"],
113
114
  projectKey: pk,
114
115
  };
115
- if (f["if-not-exists"] === true || f["dry-run"] === true) {
116
+ if (f["if-not-exists"] === true || executionMode(f).dryRun) {
116
117
  const list = await c.scenarios.list(pk);
117
118
  const existing = list.find((s) => s.id === a[0]);
118
- if (existing && f["if-not-exists"] === true && f["dry-run"] !== true) {
119
+ if (existing && f["if-not-exists"] === true && !executionMode(f).dryRun) {
119
120
  return skipResult("scenario", a[0], "exists", { current: existing, });
120
121
  }
121
- if (f["dry-run"] === true) {
122
+ if (executionMode(f).dryRun) {
122
123
  return {
123
124
  dryRun: true,
124
125
  action: "create",
@@ -150,7 +151,7 @@ export const scenarioCommands = {
150
151
  throw new UsageError("--data, --data-file, or --stdin is required. Usage: dss scenario update <id> [--data '{...}' | --data-file PATH | --stdin]");
151
152
  }
152
153
  const pk = f["project-key"];
153
- if (f["dry-run"] === true) {
154
+ if (executionMode(f).dryRun) {
154
155
  const current = await c.scenarios.get(a[0], { projectKey: pk, });
155
156
  const preview = scenarioUpdatePreview(current, data);
156
157
  return {
@@ -187,4 +188,141 @@ export const scenarioCommands = {
187
188
  "dss scenario update my_scenario --data-file settings.json --dry-run",
188
189
  ],
189
190
  },
191
+ abort: {
192
+ handler: async (c, a, f) => {
193
+ requireArgs(a, 1, "dss scenario abort <id>");
194
+ const pk = f["project-key"];
195
+ if (executionMode(f).dryRun) {
196
+ return {
197
+ dryRun: true,
198
+ action: "abort",
199
+ resource: "scenario",
200
+ id: a[0],
201
+ endpoint: encodedProjectEndpoint(c, pk, `/scenarios/${encodeURIComponent(a[0])}/abort`),
202
+ method: "POST",
203
+ };
204
+ }
205
+ await c.scenarios.abort(a[0], pk);
206
+ return { aborted: a[0], resource: "scenario", };
207
+ },
208
+ usage: "dss scenario abort <id> [--dry-run] [--project-key KEY]",
209
+ description: "Abort a running scenario. Returns when DSS accepts the abort; the scenario may take time to stop.",
210
+ examples: ["dss scenario abort my_scenario",],
211
+ },
212
+ "last-runs": {
213
+ handler: (c, a, f) => {
214
+ requireArgs(a, 1, "dss scenario last-runs <id>");
215
+ return c.scenarios.getLastRuns(a[0], {
216
+ limit: num(f["limit"], "--limit"),
217
+ projectKey: f["project-key"],
218
+ });
219
+ },
220
+ usage: "dss scenario last-runs <id> [--limit N] [--project-key KEY]",
221
+ description: "List the most recent runs of a scenario (run id, start/end, outcome).",
222
+ examples: ["dss scenario last-runs my_scenario --limit 10",],
223
+ },
224
+ "get-run": {
225
+ handler: (c, a, f) => {
226
+ requireArgs(a, 2, "dss scenario get-run <id> <runId>");
227
+ return c.scenarios.getRunDetails(a[0], a[1], {
228
+ projectKey: f["project-key"],
229
+ });
230
+ },
231
+ usage: "dss scenario get-run <id> <runId> [--project-key KEY]",
232
+ description: "Get the details of a specific scenario run, including per-step outcomes.",
233
+ examples: ["dss scenario get-run my_scenario 2024-09-01-01-02-03-123",],
234
+ },
235
+ log: {
236
+ handler: async (c, a, f) => {
237
+ requireArgs(a, 2, "dss scenario log <id> <runId>");
238
+ const result = await c.scenarios.getRunLog(a[0], a[1], {
239
+ stepId: f["step-id"],
240
+ maxLogBytes: num(f["max-log-bytes"], "--max-log-bytes"),
241
+ projectKey: f["project-key"],
242
+ });
243
+ const outputFile = f["output"]
244
+ ?? f["output-file"];
245
+ if (outputFile) {
246
+ const { mkdir, writeFile, } = await import("node:fs/promises");
247
+ const { dirname, resolve, } = await import("node:path");
248
+ const outputPath = resolve(outputFile);
249
+ await mkdir(dirname(outputPath), { recursive: true, });
250
+ await writeFile(outputPath, result.text, "utf-8");
251
+ return { path: outputPath, truncated: result.truncated, };
252
+ }
253
+ return result.text;
254
+ },
255
+ usage: "dss scenario log <id> <runId> [--step-id STEP_ID] [--max-log-bytes N] [--output PATH] [--project-key KEY]",
256
+ description: "Get the log of a scenario run (or one step via --step-id). Byte-bounded at --max-log-bytes (default 1 MiB); --output PATH writes it to a file (stdout returns the path).",
257
+ examples: [
258
+ "dss scenario log my_scenario 2024-09-01-01-02-03-123",
259
+ "dss scenario log my_scenario RUN_ID --step-id prepare_1 --max-log-bytes 100000",
260
+ ],
261
+ },
262
+ "payload-get": {
263
+ handler: (c, a, f) => {
264
+ requireArgs(a, 1, "dss scenario payload-get <id>");
265
+ return c.scenarios.getPayload(a[0], { projectKey: f["project-key"], });
266
+ },
267
+ usage: "dss scenario payload-get <id> [--project-key KEY]",
268
+ description: "Get the payload of a custom scenario (e.g. its Python script).",
269
+ examples: ["dss scenario payload-get my_custom_scenario",],
270
+ },
271
+ "payload-set": {
272
+ handler: async (c, a, f) => {
273
+ requireArgs(a, 1, "dss scenario payload-set <id> (--data '{...}' | --data-file PATH | --stdin)");
274
+ const payload = jsonInput(f);
275
+ if (payload === undefined) {
276
+ throw new UsageError("--data, --data-file, or --stdin is required. Usage: dss scenario payload-set <id> (--data '{...}' | --data-file PATH | --stdin)");
277
+ }
278
+ const pk = f["project-key"];
279
+ if (executionMode(f).dryRun) {
280
+ return {
281
+ dryRun: true,
282
+ action: "payload-set",
283
+ resource: "scenario",
284
+ id: a[0],
285
+ payload,
286
+ endpoint: encodedProjectEndpoint(c, pk, `/scenarios/${encodeURIComponent(a[0])}/payload`),
287
+ method: "PUT",
288
+ };
289
+ }
290
+ await c.scenarios.setPayload(a[0], payload, { projectKey: pk, });
291
+ return { updated: a[0], resource: "scenario", part: "payload", };
292
+ },
293
+ usage: "dss scenario payload-set <id> (--data '{...}' | --data-file PATH | --stdin) [--dry-run] [--project-key KEY]",
294
+ description: "Update the payload of a custom scenario (e.g. replace its Python script).",
295
+ examples: [
296
+ 'dss scenario payload-set my_custom_scenario --data \'{"script":"print(1)"}\' --dry-run',
297
+ "dss scenario payload-set my_custom_scenario --data-file payload.json",
298
+ ],
299
+ },
300
+ "active-set": {
301
+ handler: async (c, a, f) => {
302
+ requireArgs(a, 2, "dss scenario active-set <id> <true|false>");
303
+ const active = parseBooleanOption(a[1], "active");
304
+ if (active === undefined) {
305
+ throw new UsageError("active must be 'true' or 'false'. Usage: dss scenario active-set <id> <true|false>");
306
+ }
307
+ const pk = f["project-key"];
308
+ if (executionMode(f).dryRun) {
309
+ return {
310
+ dryRun: true,
311
+ action: "active-set",
312
+ resource: "scenario",
313
+ id: a[0],
314
+ active,
315
+ endpoint: encodedProjectEndpoint(c, pk, `/scenarios/${encodeURIComponent(a[0])}/light`),
316
+ method: "PUT",
317
+ };
318
+ }
319
+ return c.scenarios.setActive(a[0], active, { projectKey: pk, });
320
+ },
321
+ usage: "dss scenario active-set <id> <true|false> [--dry-run] [--project-key KEY]",
322
+ description: "Activate or deactivate a scenario (light update; deactivated scenarios ignore triggers).",
323
+ examples: [
324
+ "dss scenario active-set my_scenario false",
325
+ "dss scenario active-set my_scenario true --dry-run",
326
+ ],
327
+ },
190
328
  };