dataiku-sdk 4.0.0 → 4.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +1 -0
- package/dist/build-metadata.json +1 -1
- package/dist/packages/types/src/index.d.ts +5 -1
- package/dist/packages/types/src/index.js +5 -1
- package/dist/src/cli/command-syntax.json +4136 -2756
- package/dist/src/cli/commands/code-env.js +5 -3
- package/dist/src/cli/commands/dataset.js +6 -4
- package/dist/src/cli/commands/future.js +7 -2
- package/dist/src/cli/commands/index.js +4 -0
- package/dist/src/cli/commands/job.js +12 -9
- package/dist/src/cli/commands/messaging-channel.d.ts +17 -0
- package/dist/src/cli/commands/messaging-channel.js +385 -0
- package/dist/src/cli/commands/project-deployer.js +33 -0
- package/dist/src/cli/commands/project.js +30 -0
- package/dist/src/cli/commands/recipe.js +23 -2
- package/dist/src/cli/commands/scenario.d.ts +6 -0
- package/dist/src/cli/commands/scenario.js +495 -17
- package/dist/src/cli/contract.js +20 -4
- package/dist/src/cli/env.d.ts +6 -0
- package/dist/src/cli/env.js +8 -0
- package/dist/src/cli/flags.js +34 -0
- package/dist/src/cli/helpers/cleanup.js +11 -0
- package/dist/src/cli/list-shapes.js +5 -0
- package/dist/src/cli/output.d.ts +2 -0
- package/dist/src/cli/output.js +24 -4
- package/dist/src/cli/plans.js +94 -2
- package/dist/src/cli/read-shapes.js +92 -1
- package/dist/src/cli/result-shapes.d.ts +19 -1
- package/dist/src/cli/result-shapes.js +228 -4
- package/dist/src/cli/selector-suggestions.d.ts +14 -0
- package/dist/src/cli/selector-suggestions.js +56 -0
- package/dist/src/cli/usage.d.ts +1 -1
- package/dist/src/cli/usage.js +36 -4
- package/dist/src/cli/version.d.ts +2 -2
- package/dist/src/cli/version.js +2 -2
- package/dist/src/cli.js +62 -13
- package/dist/src/client.d.ts +16 -4
- package/dist/src/client.js +45 -9
- package/dist/src/errors.d.ts +2 -0
- package/dist/src/errors.js +12 -2
- package/dist/src/generated/action-output-schemas.json +296 -0
- package/dist/src/index.d.ts +1 -0
- package/dist/src/index.js +1 -0
- package/dist/src/resources/bundles.d.ts +18 -0
- package/dist/src/resources/bundles.js +26 -0
- package/dist/src/resources/futures.d.ts +3 -0
- package/dist/src/resources/futures.js +5 -1
- package/dist/src/resources/messaging-channels.d.ts +83 -0
- package/dist/src/resources/messaging-channels.js +108 -0
- package/dist/src/resources/projects.d.ts +24 -0
- package/dist/src/resources/projects.js +36 -0
- package/dist/src/resources/recipe-create.d.ts +4 -1
- package/dist/src/resources/recipe-create.js +11 -2
- package/dist/src/resources/scenario-authoring.d.ts +52 -0
- package/dist/src/resources/scenario-authoring.js +216 -0
- package/dist/src/resources/scenarios.d.ts +128 -11
- package/dist/src/resources/scenarios.js +273 -43
- package/dist/src/resources/sql.js +3 -3
- package/dist/src/utils/messaging-channel-secrets.d.ts +17 -0
- package/dist/src/utils/messaging-channel-secrets.js +114 -0
- package/package.json +2 -1
- package/plugin.json +1 -1
- package/skills/dataiku-dss/SKILL.md +15 -16
- package/skills/dataiku-dss/references/discovery.md +2 -2
- package/skills/dataiku-dss/references/notifications.md +29 -0
- package/skills/dataiku-dss/references/troubleshooting.md +1 -0
|
@@ -1,7 +1,8 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Compact default results of the
|
|
3
|
-
* `dataset info`, `dataset get`, `project get`
|
|
4
|
-
* point for compacting
|
|
2
|
+
* Compact default results of the large read commands (`job get`,
|
|
3
|
+
* `dataset info`, `dataset get`, `project get`, `scenario get-run`,
|
|
4
|
+
* `scenario last-runs`), plus the single dispatch point for compacting
|
|
5
|
+
* command results (`shapeCommandResult`).
|
|
5
6
|
*
|
|
6
7
|
* The SDK resources keep returning the raw DSS objects; only the CLI compacts.
|
|
7
8
|
* `--full` returns the DSS object unchanged, and `--fields` projects DSS paths
|
|
@@ -213,16 +214,239 @@ function shapeProjectGet(result) {
|
|
|
213
214
|
const pruned = record(pruneEmpty(settings));
|
|
214
215
|
return { ...pruned, deniedPermissions, };
|
|
215
216
|
}
|
|
217
|
+
/** Scenario/step/job outcomes that count as a failure worth diagnosing. */
|
|
218
|
+
const FAILED_OUTCOMES = { FAILED: true, ABORTED: true, };
|
|
219
|
+
function isFailedOutcome(value) {
|
|
220
|
+
return typeof value === "string" && FAILED_OUTCOMES[value] === true;
|
|
221
|
+
}
|
|
222
|
+
/** Message of a DSS `thrown` exception object (`{ clazz, message, ... }`). */
|
|
223
|
+
function thrownMessage(value) {
|
|
224
|
+
const thrown = record(value);
|
|
225
|
+
return nonEmptyString(thrown["message"]) ?? nonEmptyString(thrown["detailedMessage"]);
|
|
226
|
+
}
|
|
227
|
+
function reportItems(stepRun, type) {
|
|
228
|
+
const items = stepRun["additionalReportItems"];
|
|
229
|
+
return Array.isArray(items)
|
|
230
|
+
? items.filter((item) => isRecord(item) && item["type"] === type)
|
|
231
|
+
: [];
|
|
232
|
+
}
|
|
233
|
+
/**
|
|
234
|
+
* One `stepRuns[]` entry: the step identity and outcome, the datasets it built
|
|
235
|
+
* (`BUILT_DATASET` report items) and the jobs it ran (`JOB_EXECUTED`, with the
|
|
236
|
+
* thrown message). The job log tail DSS embeds in the report is dropped.
|
|
237
|
+
*/
|
|
238
|
+
function shapeScenarioStep(stepRun, runProjectKey) {
|
|
239
|
+
const fields = record(stepRun);
|
|
240
|
+
const step = record(fields["step"]);
|
|
241
|
+
const result = record(fields["result"]);
|
|
242
|
+
const datasets = reportItems(fields, "BUILT_DATASET").map((item) => {
|
|
243
|
+
const target = record(item["target"]);
|
|
244
|
+
const partition = target["partition"];
|
|
245
|
+
return defined({
|
|
246
|
+
dataset: target["datasetName"],
|
|
247
|
+
projectKey: typeof target["projectKey"] === "string" && target["projectKey"] !== runProjectKey
|
|
248
|
+
? target["projectKey"]
|
|
249
|
+
: undefined,
|
|
250
|
+
partition: typeof partition === "string" && partition !== "NP" ? partition : undefined,
|
|
251
|
+
outcome: item["outcome"],
|
|
252
|
+
});
|
|
253
|
+
});
|
|
254
|
+
const jobs = reportItems(fields, "JOB_EXECUTED").map((item) => defined({
|
|
255
|
+
jobId: item["jobId"],
|
|
256
|
+
outcome: item["outcome"],
|
|
257
|
+
message: thrownMessage(item["thrown"]),
|
|
258
|
+
}));
|
|
259
|
+
return defined({
|
|
260
|
+
id: step["id"],
|
|
261
|
+
name: step["name"],
|
|
262
|
+
type: step["type"],
|
|
263
|
+
outcome: result["outcome"],
|
|
264
|
+
message: thrownMessage(result["thrown"]),
|
|
265
|
+
datasets: datasets.length > 0 ? datasets : undefined,
|
|
266
|
+
jobs: jobs.length > 0 ? jobs : undefined,
|
|
267
|
+
});
|
|
268
|
+
}
|
|
269
|
+
/**
|
|
270
|
+
* First failure of a shaped run: the failed step, its failed job and dataset,
|
|
271
|
+
* and the most specific message (job, then step, then scenario).
|
|
272
|
+
*/
|
|
273
|
+
function firstScenarioFailure(steps, runThrown) {
|
|
274
|
+
for (const step of steps) {
|
|
275
|
+
const jobs = step["jobs"] ?? [];
|
|
276
|
+
const job = jobs.find((entry) => isFailedOutcome(entry["outcome"]));
|
|
277
|
+
if (!isFailedOutcome(step["outcome"]) && !job)
|
|
278
|
+
continue;
|
|
279
|
+
const datasets = step["datasets"] ?? [];
|
|
280
|
+
const dataset = datasets.find((entry) => isFailedOutcome(entry["outcome"]));
|
|
281
|
+
return defined({
|
|
282
|
+
stepId: step["id"],
|
|
283
|
+
jobId: job?.["jobId"],
|
|
284
|
+
dataset: dataset?.["dataset"],
|
|
285
|
+
message: nonEmptyString(job?.["message"]) ?? nonEmptyString(step["message"])
|
|
286
|
+
?? thrownMessage(runThrown),
|
|
287
|
+
});
|
|
288
|
+
}
|
|
289
|
+
const message = thrownMessage(runThrown);
|
|
290
|
+
return message === undefined ? undefined : { message, };
|
|
291
|
+
}
|
|
292
|
+
function projectKeyArgs(projectKey) {
|
|
293
|
+
return typeof projectKey === "string" && projectKey.length > 0
|
|
294
|
+
? ["--project-key", projectKey,]
|
|
295
|
+
: [];
|
|
296
|
+
}
|
|
297
|
+
/**
|
|
298
|
+
* Next-step argv for a failure. With the failed recipe known: its code and the
|
|
299
|
+
* schemas of its inputs (what a "column not found" or type error needs), then
|
|
300
|
+
* the job's error log; else the job's error log, else the scenario run log.
|
|
301
|
+
*/
|
|
302
|
+
function failureNextSteps(failure, scenarioId, runId, projectKey, activity) {
|
|
303
|
+
const pk = projectKeyArgs(projectKey);
|
|
304
|
+
const inspect = activity
|
|
305
|
+
? [
|
|
306
|
+
["dss", "recipe", "cat", activity.recipe, ...pk,],
|
|
307
|
+
...activity.inputs.slice(0, 3).map((input) => ["dss", "dataset", "schema", input, ...pk,]),
|
|
308
|
+
]
|
|
309
|
+
: [];
|
|
310
|
+
if (typeof failure["jobId"] === "string") {
|
|
311
|
+
return [...inspect, ["dss", "job", "log", failure["jobId"], "--errors-only", ...pk,],];
|
|
312
|
+
}
|
|
313
|
+
if (typeof scenarioId !== "string" || typeof runId !== "string")
|
|
314
|
+
return undefined;
|
|
315
|
+
const stepArgs = typeof failure["stepId"] === "string" ? ["--step-id", failure["stepId"],] : [];
|
|
316
|
+
return [["dss", "scenario", "log", scenarioId, runId, ...stepArgs, ...pk,],];
|
|
317
|
+
}
|
|
318
|
+
/** Scenario identity of a run object (`scenarioRun` or a last-runs item). */
|
|
319
|
+
function runScenarioIdentity(run) {
|
|
320
|
+
const scenario = record(run["scenario"]);
|
|
321
|
+
const target = record(record(run["result"])["target"]);
|
|
322
|
+
return {
|
|
323
|
+
scenarioId: scenario["id"] ?? target["scenarioId"],
|
|
324
|
+
projectKey: scenario["projectKey"] ?? target["projectKey"],
|
|
325
|
+
};
|
|
326
|
+
}
|
|
327
|
+
function runTrigger(run) {
|
|
328
|
+
const trigger = record(record(run["trigger"])["trigger"]);
|
|
329
|
+
const shaped = defined({
|
|
330
|
+
type: nonEmptyString(trigger["type"]),
|
|
331
|
+
name: nonEmptyString(trigger["name"]),
|
|
332
|
+
});
|
|
333
|
+
return Object.keys(shaped).length > 0 ? shaped : undefined;
|
|
334
|
+
}
|
|
335
|
+
/**
|
|
336
|
+
* Failure diagnosis of a raw `scenario get-run` report: the first failure
|
|
337
|
+
* (`{ stepId, jobId, dataset, message }`) and the next command(s) to run as
|
|
338
|
+
* argv arrays. With the failed recipe known, `hint` names the edit-and-rerun
|
|
339
|
+
* loop, so the agent does not look up the write commands. Empty when the run did not fail.
|
|
340
|
+
*/
|
|
341
|
+
export function scenarioRunDiagnosis(details, activity) {
|
|
342
|
+
const fields = record(details);
|
|
343
|
+
const run = record(fields["scenarioRun"]);
|
|
344
|
+
const result = record(run["result"]);
|
|
345
|
+
const { scenarioId, projectKey, } = runScenarioIdentity(run);
|
|
346
|
+
const steps = Array.isArray(fields["stepRuns"])
|
|
347
|
+
? fields["stepRuns"].map((stepRun) => shapeScenarioStep(stepRun, projectKey))
|
|
348
|
+
: [];
|
|
349
|
+
const failed = isFailedOutcome(result["outcome"])
|
|
350
|
+
|| steps.some((step) => isFailedOutcome(step["outcome"]));
|
|
351
|
+
const failure = failed ? firstScenarioFailure(steps, result["thrown"]) : undefined;
|
|
352
|
+
if (!failure)
|
|
353
|
+
return {};
|
|
354
|
+
if (!activity) {
|
|
355
|
+
const next = failureNextSteps(failure, scenarioId, run["runId"], projectKey, undefined);
|
|
356
|
+
return next ? { failure, next, } : { failure, };
|
|
357
|
+
}
|
|
358
|
+
const located = { ...failure, recipe: activity.recipe, inputs: activity.inputs, };
|
|
359
|
+
const next = failureNextSteps(located, scenarioId, run["runId"], projectKey, activity);
|
|
360
|
+
const pk = projectKeyArgs(projectKey).join(" ");
|
|
361
|
+
const suffix = pk === "" ? "" : ` ${pk}`;
|
|
362
|
+
const rerun = typeof scenarioId === "string"
|
|
363
|
+
? `; rerun: dss scenario run-and-wait ${scenarioId}${suffix}`
|
|
364
|
+
: "";
|
|
365
|
+
const hint = `To fix: dss recipe get-payload ${activity.recipe} --output FILE${suffix}, edit FILE, `
|
|
366
|
+
+ `dss recipe set-payload ${activity.recipe} --file FILE${suffix}${rerun}.`;
|
|
367
|
+
return next ? { failure: located, next, hint, } : { failure: located, hint, };
|
|
368
|
+
}
|
|
369
|
+
/**
|
|
370
|
+
* `scenario get-run`: run identity, outcome, timing, trigger, per-step
|
|
371
|
+
* datasets/jobs, the first failure, and next-step argv. Never the trigger
|
|
372
|
+
* auth context, run-as user, API key metadata, or job log tails.
|
|
373
|
+
*/
|
|
374
|
+
function shapeScenarioGetRun(result) {
|
|
375
|
+
if (!isRecord(result) || !isRecord(result["scenarioRun"]))
|
|
376
|
+
return result;
|
|
377
|
+
const run = result["scenarioRun"];
|
|
378
|
+
const runResult = record(run["result"]);
|
|
379
|
+
const { scenarioId, projectKey, } = runScenarioIdentity(run);
|
|
380
|
+
const start = typeof run["start"] === "number" ? run["start"] : undefined;
|
|
381
|
+
const end = typeof run["end"] === "number" ? run["end"] : undefined;
|
|
382
|
+
const steps = Array.isArray(result["stepRuns"])
|
|
383
|
+
? result["stepRuns"].map((stepRun) => shapeScenarioStep(stepRun, projectKey))
|
|
384
|
+
: [];
|
|
385
|
+
const { failure, next, } = scenarioRunDiagnosis(result);
|
|
386
|
+
return defined({
|
|
387
|
+
runId: run["runId"],
|
|
388
|
+
scenarioId,
|
|
389
|
+
projectKey,
|
|
390
|
+
outcome: runResult["outcome"],
|
|
391
|
+
start,
|
|
392
|
+
end,
|
|
393
|
+
durationMs: start !== undefined && end !== undefined && end >= start ? end - start : undefined,
|
|
394
|
+
trigger: runTrigger(run),
|
|
395
|
+
steps,
|
|
396
|
+
failure,
|
|
397
|
+
next,
|
|
398
|
+
});
|
|
399
|
+
}
|
|
400
|
+
/**
|
|
401
|
+
* One `scenario last-runs` item: identity, outcome, timing, trigger, failure
|
|
402
|
+
* message, and for a failed run the `scenario get-run` argv that shows why.
|
|
403
|
+
*/
|
|
404
|
+
function shapeScenarioRunSummary(item) {
|
|
405
|
+
if (!isRecord(item))
|
|
406
|
+
return item;
|
|
407
|
+
const result = record(item["result"]);
|
|
408
|
+
const message = thrownMessage(result["thrown"]);
|
|
409
|
+
const { scenarioId, projectKey, } = runScenarioIdentity(item);
|
|
410
|
+
const next = isFailedOutcome(result["outcome"]) && typeof scenarioId === "string"
|
|
411
|
+
&& typeof item["runId"] === "string"
|
|
412
|
+
? [[
|
|
413
|
+
"dss",
|
|
414
|
+
"scenario",
|
|
415
|
+
"get-run",
|
|
416
|
+
scenarioId,
|
|
417
|
+
item["runId"],
|
|
418
|
+
...(typeof projectKey === "string" ? ["--project-key", projectKey,] : []),
|
|
419
|
+
],]
|
|
420
|
+
: undefined;
|
|
421
|
+
// DSS lists an in-progress run with `end: 0` and no result outcome.
|
|
422
|
+
const running = result["outcome"] === undefined && item["end"] === 0;
|
|
423
|
+
return defined({
|
|
424
|
+
runId: item["runId"],
|
|
425
|
+
outcome: running ? "RUNNING" : result["outcome"],
|
|
426
|
+
start: item["start"] ?? result["start"],
|
|
427
|
+
end: item["end"] ?? result["end"],
|
|
428
|
+
trigger: runTrigger(item),
|
|
429
|
+
failure: message === undefined ? undefined : { message, },
|
|
430
|
+
next,
|
|
431
|
+
});
|
|
432
|
+
}
|
|
433
|
+
/** `scenario last-runs`: one small object per run, newest first as DSS returns them. */
|
|
434
|
+
function shapeScenarioLastRuns(result) {
|
|
435
|
+
return Array.isArray(result) ? result.map(shapeScenarioRunSummary) : result;
|
|
436
|
+
}
|
|
216
437
|
/** Compact read shapers by `resource.action`. */
|
|
217
438
|
const READ_SHAPERS = {
|
|
218
439
|
"job.get": shapeJobGet,
|
|
219
440
|
"dataset.info": shapeDatasetInfo,
|
|
220
441
|
"dataset.get": shapeDatasetGet,
|
|
221
442
|
"project.get": shapeProjectGet,
|
|
443
|
+
"scenario.get-run": shapeScenarioGetRun,
|
|
444
|
+
"scenario.last-runs": shapeScenarioLastRuns,
|
|
445
|
+
"scenario.runs-by-date": shapeScenarioLastRuns,
|
|
222
446
|
};
|
|
223
447
|
/**
|
|
224
448
|
* Compact a command result for agents: `*.list` items (filter, cap, compact
|
|
225
|
-
* fields) and the
|
|
449
|
+
* fields) and the large read commands. `--full` and `--fields` bypass
|
|
226
450
|
* the read shapers so the DSS object (or a projection of it) is returned.
|
|
227
451
|
*/
|
|
228
452
|
export function shapeCommandResult(resource, action, result, flags) {
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Did-you-mean suggestions for `commands run --fields RESOURCE[.ACTION[.FIELD...]]`
|
|
3
|
+
* selectors that name no resource or action. Pure data in, strings out.
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* Selectors worth trying instead of an unknown `resource` / `resource.action`
|
|
7
|
+
* selector, keeping any trailing `.field` path. For a known resource: the same
|
|
8
|
+
* action name under other resources (`flow.map.usage` -> `project.map.usage`),
|
|
9
|
+
* then close action names of that resource. For an unknown resource: close
|
|
10
|
+
* resource names (keeping the action), else the action under other resources.
|
|
11
|
+
*
|
|
12
|
+
* @param actionsByResource resource -> action names (`commandActionSummary()`).
|
|
13
|
+
*/
|
|
14
|
+
export declare function suggestSelectors(selector: string, actionsByResource: Record<string, string[]>): string[];
|
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Did-you-mean suggestions for `commands run --fields RESOURCE[.ACTION[.FIELD...]]`
|
|
3
|
+
* selectors that name no resource or action. Pure data in, strings out.
|
|
4
|
+
*/
|
|
5
|
+
const MAX_SUGGESTIONS = 4;
|
|
6
|
+
/** Levenshtein distance with a single rolling row. */
|
|
7
|
+
function editDistance(a, b) {
|
|
8
|
+
let previous = Array.from({ length: b.length + 1, }, (_, index) => index);
|
|
9
|
+
for (let i = 1; i <= a.length; i++) {
|
|
10
|
+
const current = [i,];
|
|
11
|
+
for (let j = 1; j <= b.length; j++) {
|
|
12
|
+
const cost = a[i - 1] === b[j - 1] ? 0 : 1;
|
|
13
|
+
current.push(Math.min(previous[j] + 1, current[j - 1] + 1, previous[j - 1] + cost));
|
|
14
|
+
}
|
|
15
|
+
previous = current;
|
|
16
|
+
}
|
|
17
|
+
return previous[b.length];
|
|
18
|
+
}
|
|
19
|
+
/** Names close to `name`: a typo (distance <= 2, <= 1 for short names) or one containing it. */
|
|
20
|
+
function closeNames(name, candidates) {
|
|
21
|
+
const maxDistance = name.length >= 5 ? 2 : 1;
|
|
22
|
+
return candidates.filter((candidate) => candidate !== name
|
|
23
|
+
&& (editDistance(name, candidate) <= maxDistance
|
|
24
|
+
|| (name.length >= 3 && candidate.includes(name))));
|
|
25
|
+
}
|
|
26
|
+
/**
|
|
27
|
+
* Selectors worth trying instead of an unknown `resource` / `resource.action`
|
|
28
|
+
* selector, keeping any trailing `.field` path. For a known resource: the same
|
|
29
|
+
* action name under other resources (`flow.map.usage` -> `project.map.usage`),
|
|
30
|
+
* then close action names of that resource. For an unknown resource: close
|
|
31
|
+
* resource names (keeping the action), else the action under other resources.
|
|
32
|
+
*
|
|
33
|
+
* @param actionsByResource resource -> action names (`commandActionSummary()`).
|
|
34
|
+
*/
|
|
35
|
+
export function suggestSelectors(selector, actionsByResource) {
|
|
36
|
+
const [resource = "", action, ...rest] = selector.split(".");
|
|
37
|
+
const suffix = rest.length > 0 ? `.${rest.join(".")}` : "";
|
|
38
|
+
const resources = Object.keys(actionsByResource);
|
|
39
|
+
const sameActionElsewhere = action === undefined
|
|
40
|
+
? []
|
|
41
|
+
: resources.filter((other) => other !== resource && actionsByResource[other].includes(action))
|
|
42
|
+
.map((other) => `${other}.${action}${suffix}`);
|
|
43
|
+
let suggestions;
|
|
44
|
+
if (Object.hasOwn(actionsByResource, resource)) {
|
|
45
|
+
const closeActions = action === undefined
|
|
46
|
+
? []
|
|
47
|
+
: closeNames(action, actionsByResource[resource]).map((close) => `${resource}.${close}${suffix}`);
|
|
48
|
+
suggestions = [...sameActionElsewhere, ...closeActions,];
|
|
49
|
+
}
|
|
50
|
+
else {
|
|
51
|
+
const tail = action === undefined ? "" : `.${[action, ...rest,].join(".")}`;
|
|
52
|
+
const closeResources = closeNames(resource, resources).map((close) => `${close}${tail}`);
|
|
53
|
+
suggestions = closeResources.length > 0 ? closeResources : sameActionElsewhere;
|
|
54
|
+
}
|
|
55
|
+
return [...new Set(suggestions),].slice(0, MAX_SUGGESTIONS);
|
|
56
|
+
}
|
package/dist/src/cli/usage.d.ts
CHANGED
|
@@ -11,7 +11,7 @@ export declare const COMMANDS_RUN_HINT = "Use `dss commands run --fields RESOURC
|
|
|
11
11
|
export declare function unsupportedHelpFlag(): UsageError;
|
|
12
12
|
export declare function noCommandError(): UsageError;
|
|
13
13
|
export declare function missingActionError(resource: string, validActions: string[], usage?: string): UsageError;
|
|
14
|
-
export declare function unknownResourceError(resource: string): UsageError;
|
|
14
|
+
export declare function unknownResourceError(resource: string, action?: string): UsageError;
|
|
15
15
|
/**
|
|
16
16
|
* Agents rarely mistype; they reuse plausible flags from other commands or
|
|
17
17
|
* CLIs. When the command is known, the hint is its usage line, which lists
|
package/dist/src/cli/usage.js
CHANGED
|
@@ -1,4 +1,29 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { suggestSelectors, } from "./selector-suggestions.js";
|
|
2
|
+
import { commandSyntaxKeys, commandSyntaxTree, renderNodes, syntaxHasFlag, } from "./syntax.js";
|
|
3
|
+
/**
|
|
4
|
+
* `dss resource action` commands close to a mistyped one (`flow map` -> `project map`,
|
|
5
|
+
* `recipes list` -> `recipe list`), from the syntax tree, so the error names the fix.
|
|
6
|
+
*/
|
|
7
|
+
function commandSuggestions(resource, action) {
|
|
8
|
+
const actionsByResource = {};
|
|
9
|
+
for (const key of commandSyntaxKeys()) {
|
|
10
|
+
const dot = key.indexOf(".");
|
|
11
|
+
(actionsByResource[key.slice(0, dot)] ??= []).push(key.slice(dot + 1));
|
|
12
|
+
}
|
|
13
|
+
// An unknown resource with no close name gets no suggestions: the same action under
|
|
14
|
+
// arbitrary resources (`pipelines list` -> `app list`) misleads more than it helps.
|
|
15
|
+
if (!Object.hasOwn(actionsByResource, resource)
|
|
16
|
+
&& suggestSelectors(resource, actionsByResource).length === 0) {
|
|
17
|
+
return [];
|
|
18
|
+
}
|
|
19
|
+
return suggestSelectors(action ? `${resource}.${action}` : resource, actionsByResource)
|
|
20
|
+
.map((selector) => `dss ${selector.replace(".", " ")}`);
|
|
21
|
+
}
|
|
22
|
+
function suggestionHint(suggestions) {
|
|
23
|
+
return suggestions.length > 0
|
|
24
|
+
? `Did you mean ${suggestions.map((command) => `\`${command}\``).join(" or ")}? ${COMMANDS_RUN_HINT}`
|
|
25
|
+
: COMMANDS_RUN_HINT;
|
|
26
|
+
}
|
|
2
27
|
export const RESOURCE_NAMES = [
|
|
3
28
|
"agent",
|
|
4
29
|
"app",
|
|
@@ -25,6 +50,7 @@ export const RESOURCE_NAMES = [
|
|
|
25
50
|
"statistics",
|
|
26
51
|
"discussion",
|
|
27
52
|
"meaning",
|
|
53
|
+
"messaging-channel",
|
|
28
54
|
"workspace",
|
|
29
55
|
"metrics",
|
|
30
56
|
"doctor",
|
|
@@ -122,8 +148,13 @@ export function noCommandError() {
|
|
|
122
148
|
export function missingActionError(resource, validActions, usage) {
|
|
123
149
|
return new UsageError(`Missing action for ${resource}.`, "usage_error", usage ?? COMMANDS_RUN_HINT, { resource, validActions, });
|
|
124
150
|
}
|
|
125
|
-
export function unknownResourceError(resource) {
|
|
126
|
-
|
|
151
|
+
export function unknownResourceError(resource, action) {
|
|
152
|
+
const suggestions = commandSuggestions(resource, action);
|
|
153
|
+
return new UsageError(`Unknown resource: ${resource}.`, "usage_error", suggestionHint(suggestions), {
|
|
154
|
+
resource,
|
|
155
|
+
validResources: RESOURCE_NAMES,
|
|
156
|
+
...(suggestions.length > 0 ? { suggestions, } : {}),
|
|
157
|
+
});
|
|
127
158
|
}
|
|
128
159
|
/**
|
|
129
160
|
* Agents rarely mistype; they reuse plausible flags from other commands or
|
|
@@ -141,7 +172,8 @@ export function unknownFlagError(flagLabel, resource, action) {
|
|
|
141
172
|
return new UsageError(message, "unknown_flag", command ? `Usage: ${renderNodes(command)}` : undefined);
|
|
142
173
|
}
|
|
143
174
|
export function unknownActionError(resource, action, validActions, hint) {
|
|
144
|
-
|
|
175
|
+
const suggestions = commandSuggestions(resource, action);
|
|
176
|
+
return new UsageError(`Unknown action: ${resource} ${action ?? ""}`.trim(), "usage_error", hint ?? suggestionHint(suggestions), { resource, action, validActions, ...(suggestions.length > 0 ? { suggestions, } : {}), });
|
|
145
177
|
}
|
|
146
178
|
export function requireArgs(args, count, usage) {
|
|
147
179
|
if (args.length < count) {
|
|
@@ -49,7 +49,7 @@ export declare function buildVersionPayload(input: {
|
|
|
49
49
|
export declare const PACKAGE_ROOT: string | undefined;
|
|
50
50
|
export declare const CLI_VERSION: string;
|
|
51
51
|
export declare const CLI_GIT_REVISION: string | undefined;
|
|
52
|
-
export declare const AGENT_CONTRACT_VERSION =
|
|
52
|
+
export declare const AGENT_CONTRACT_VERSION = 7;
|
|
53
53
|
export declare const JSON_SCHEMA_DRAFT = "https://json-schema.org/draft/2020-12/schema";
|
|
54
|
-
export declare const AGENT_CONTRACT_SCHEMA_ID = "https://clssck.github.io/dataiku-sdk/schemas/agent-contract-
|
|
54
|
+
export declare const AGENT_CONTRACT_SCHEMA_ID = "https://clssck.github.io/dataiku-sdk/schemas/agent-contract-v7.json";
|
|
55
55
|
export declare function cliVersionResult(): CliVersionPayload;
|
package/dist/src/cli/version.js
CHANGED
|
@@ -203,9 +203,9 @@ export function buildVersionPayload(input) {
|
|
|
203
203
|
export const PACKAGE_ROOT = findPackageRoot();
|
|
204
204
|
export const CLI_VERSION = packageVersion(PACKAGE_ROOT);
|
|
205
205
|
export const CLI_GIT_REVISION = gitRevision(PACKAGE_ROOT);
|
|
206
|
-
export const AGENT_CONTRACT_VERSION =
|
|
206
|
+
export const AGENT_CONTRACT_VERSION = 7;
|
|
207
207
|
export const JSON_SCHEMA_DRAFT = "https://json-schema.org/draft/2020-12/schema";
|
|
208
|
-
export const AGENT_CONTRACT_SCHEMA_ID = "https://clssck.github.io/dataiku-sdk/schemas/agent-contract-
|
|
208
|
+
export const AGENT_CONTRACT_SCHEMA_ID = "https://clssck.github.io/dataiku-sdk/schemas/agent-contract-v7.json";
|
|
209
209
|
export function cliVersionResult() {
|
|
210
210
|
// Only code actually loaded from dist/ may report dist provenance; an
|
|
211
211
|
// inherited DSS_LOAD_SOURCE on a source run is ignored.
|
package/dist/src/cli.js
CHANGED
|
@@ -6,9 +6,10 @@ import { commands, } from "./cli/commands/index.js";
|
|
|
6
6
|
import { ambientProjectKey, loadEnvFile, } from "./cli/env.js";
|
|
7
7
|
import { executionMode, FLAG_ALIASES, isFlagValueToken, parseArgs, rawPositionals, } from "./cli/flags.js";
|
|
8
8
|
import { cleanupInputIdentifierField, cleanupLedgerEntry, } from "./cli/helpers/cleanup.js";
|
|
9
|
-
import { commandFailureExitCode, CommandResultFailure, enqueueCliWarning, flushCliWarnings, isFailedWaitResult, planResult, projectResultFields, setOutputFieldProjection, writeCommandResult, } from "./cli/output.js";
|
|
9
|
+
import { commandFailureExitCode, CommandResultFailure, enqueueCliWarning, flushCliWarnings, isFailedWaitResult, planResult, projectResultFields, setOutputFieldProjection, stdoutJson, writeCommandResult, } from "./cli/output.js";
|
|
10
10
|
import { shapeCommandResult, } from "./cli/result-shapes.js";
|
|
11
11
|
import { currentCommandContext, resolveCredentials, resolveLoginCredentials, } from "./cli/runtime.js";
|
|
12
|
+
import { suggestSelectors, } from "./cli/selector-suggestions.js";
|
|
12
13
|
import { commandSyntaxTree, commandUsage, } from "./cli/syntax.js";
|
|
13
14
|
import { COMMANDS_RUN_HINT, inferRequiresProject, missingActionError, noCommandError, requireArgs, RESOURCE_NAMES, unknownActionError, unknownFlagError, unknownResourceError, UsageError, } from "./cli/usage.js";
|
|
14
15
|
import { cliVersionResult, } from "./cli/version.js";
|
|
@@ -479,7 +480,7 @@ async function validateBatchStep(argv) {
|
|
|
479
480
|
return { positional, flags, resource, action: metaAction, };
|
|
480
481
|
const resourceActions = commands[resource];
|
|
481
482
|
if (!resourceActions)
|
|
482
|
-
throw unknownResourceError(resource);
|
|
483
|
+
throw unknownResourceError(resource, action);
|
|
483
484
|
if (!action) {
|
|
484
485
|
throw missingActionError(resource, Object.keys(resourceActions), `dss ${resource} <action>`);
|
|
485
486
|
}
|
|
@@ -766,6 +767,8 @@ async function commandsRunResult(flags) {
|
|
|
766
767
|
return contract.commandActionSummary();
|
|
767
768
|
}
|
|
768
769
|
const registry = selectors.length === 0 ? contract.buildCommandRegistry() : {};
|
|
770
|
+
const knownSelectors = [];
|
|
771
|
+
const unknownSelectors = {};
|
|
769
772
|
for (const selector of selectors) {
|
|
770
773
|
const selectorParts = selector.split(".");
|
|
771
774
|
if (selectorParts.some((part) => part.length === 0)) {
|
|
@@ -773,16 +776,62 @@ async function commandsRunResult(flags) {
|
|
|
773
776
|
}
|
|
774
777
|
const [selectedResource, selectedAction,] = selectorParts;
|
|
775
778
|
const resourceActions = cachedCommandRegistry(selectedResource);
|
|
776
|
-
|
|
777
|
-
|
|
778
|
-
|
|
779
|
-
|
|
780
|
-
|
|
779
|
+
const error = !resourceActions
|
|
780
|
+
? unknownResourceError(selectedResource)
|
|
781
|
+
: selectedAction && !resourceActions[selectedAction]
|
|
782
|
+
? unknownActionError(selectedResource, selectedAction, Object.keys(resourceActions))
|
|
783
|
+
: undefined;
|
|
784
|
+
if (resourceActions && !error) {
|
|
785
|
+
registry[selectedResource] = resourceActions;
|
|
786
|
+
knownSelectors.push(selector);
|
|
787
|
+
continue;
|
|
788
|
+
}
|
|
789
|
+
unknownSelectors[selector] = {
|
|
790
|
+
error: error,
|
|
791
|
+
suggestions: suggestSelectors(selector, contract.commandActionSummary()),
|
|
792
|
+
};
|
|
793
|
+
}
|
|
794
|
+
const unknownNames = Object.keys(unknownSelectors);
|
|
795
|
+
if (unknownNames.length > 0) {
|
|
796
|
+
if (knownSelectors.length === 0 && selectors.length > 0) {
|
|
797
|
+
const { error, suggestions, } = unknownSelectors[unknownNames[0]];
|
|
798
|
+
// The shared error suggests `dss resource action` commands; here the fix is a selector.
|
|
799
|
+
const hint = suggestions.length === 0
|
|
800
|
+
? COMMANDS_RUN_HINT
|
|
801
|
+
: `Did you mean ${suggestions.map((name) => `\`--fields ${name}\``).join(" or ")}? ${COMMANDS_RUN_HINT}`;
|
|
802
|
+
const { suggestions: _commandSuggestions, ...details } = error.details ?? {};
|
|
803
|
+
throw new UsageError(error.message, error.code, hint, {
|
|
804
|
+
...details,
|
|
805
|
+
...(suggestions.length > 0 ? { suggestions, } : {}),
|
|
806
|
+
});
|
|
781
807
|
}
|
|
808
|
+
enqueueCliWarning({
|
|
809
|
+
code: "unknown_fields",
|
|
810
|
+
fields: unknownNames,
|
|
811
|
+
suggestions: Object.fromEntries(unknownNames.flatMap((name) => unknownSelectors[name].suggestions.length > 0
|
|
812
|
+
? [[name, unknownSelectors[name].suggestions,],]
|
|
813
|
+
: [])),
|
|
814
|
+
hint: "Unknown selectors were skipped; the result holds the known ones. Run `dss commands run` for resource/action names.",
|
|
815
|
+
});
|
|
816
|
+
}
|
|
817
|
+
// On stdout a bare RESOURCE selector lists its actions (usage, description, side effect):
|
|
818
|
+
// the full entries (flags, schemas, examples) run 30-55 KB per resource, more than an agent
|
|
819
|
+
// can read in one result. RESOURCE.ACTION and --output keep the full entries.
|
|
820
|
+
const fullSelectors = typeof outputPath === "string"
|
|
821
|
+
? knownSelectors
|
|
822
|
+
: knownSelectors.filter((selector) => selector.includes("."));
|
|
823
|
+
const selectedRegistry = selectors.length === 0 ? registry : {};
|
|
824
|
+
if (fullSelectors.length > 0) {
|
|
825
|
+
Object.assign(selectedRegistry, projectResultFields(registry, fullSelectors, () => Object.keys(contract.commandActionSummary()).sort()));
|
|
826
|
+
}
|
|
827
|
+
for (const resource of knownSelectors) {
|
|
828
|
+
if (fullSelectors.includes(resource))
|
|
829
|
+
continue;
|
|
830
|
+
selectedRegistry[resource] = Object.fromEntries(Object.entries(registry[resource]).map(([action, entry,]) => [
|
|
831
|
+
action,
|
|
832
|
+
{ usage: entry.usage, description: entry.description, sideEffect: entry.sideEffect, },
|
|
833
|
+
]));
|
|
782
834
|
}
|
|
783
|
-
const selectedRegistry = selectors.length === 0
|
|
784
|
-
? registry
|
|
785
|
-
: projectResultFields(registry, selectors, () => Object.keys(contract.commandActionSummary()).sort());
|
|
786
835
|
if (typeof outputPath === "string") {
|
|
787
836
|
const exported = selectedRegistry;
|
|
788
837
|
await Bun.write(outputPath, `${JSON.stringify(exported)}\n`);
|
|
@@ -1046,7 +1095,7 @@ async function runBatch(flags) {
|
|
|
1046
1095
|
else {
|
|
1047
1096
|
const resourceActions = commands[resource];
|
|
1048
1097
|
if (!resourceActions)
|
|
1049
|
-
throw unknownResourceError(resource);
|
|
1098
|
+
throw unknownResourceError(resource, action);
|
|
1050
1099
|
if (!action) {
|
|
1051
1100
|
throw missingActionError(resource, Object.keys(resourceActions), `dss ${resource} <action>`);
|
|
1052
1101
|
}
|
|
@@ -1461,7 +1510,7 @@ function buildErrorReport(err, contextOverride) {
|
|
|
1461
1510
|
}
|
|
1462
1511
|
function writeErrorReport(err) {
|
|
1463
1512
|
flushCliWarnings();
|
|
1464
|
-
process.stdout.write(
|
|
1513
|
+
process.stdout.write(stdoutJson(buildErrorReport(err)));
|
|
1465
1514
|
}
|
|
1466
1515
|
/** Only ambiguous mutation failures require full contract metadata. */
|
|
1467
1516
|
function errorReportNeedsCommandRuntime(err) {
|
|
@@ -1509,7 +1558,7 @@ async function main() {
|
|
|
1509
1558
|
}
|
|
1510
1559
|
// Resource and action recovery require only this resource's definitions.
|
|
1511
1560
|
if (!RESOURCE_NAMES.includes(resource))
|
|
1512
|
-
throw unknownResourceError(resource);
|
|
1561
|
+
throw unknownResourceError(resource, positional[1]);
|
|
1513
1562
|
const resourceActions = commands[resource];
|
|
1514
1563
|
if (positional.length === 1) {
|
|
1515
1564
|
throw missingActionError(resource, Object.keys(resourceActions), `dss ${resource} <action> [args...]`);
|
package/dist/src/client.d.ts
CHANGED
|
@@ -23,6 +23,7 @@ import type { KnowledgeBanksResource } from "./resources/knowledge-banks.js";
|
|
|
23
23
|
import type { LlmsResource } from "./resources/llms.js";
|
|
24
24
|
import type { MacrosResource } from "./resources/macros.js";
|
|
25
25
|
import type { MeaningsResource } from "./resources/meanings.js";
|
|
26
|
+
import type { MessagingChannelsResource } from "./resources/messaging-channels.js";
|
|
26
27
|
import type { MetricsResource } from "./resources/metrics.js";
|
|
27
28
|
import type { MlTasksResource } from "./resources/ml-tasks.js";
|
|
28
29
|
import type { MlflowExtensionResource } from "./resources/mlflow-extension.js";
|
|
@@ -196,6 +197,7 @@ export declare class DataikuClient {
|
|
|
196
197
|
private workspacesResource?;
|
|
197
198
|
private metricsResource?;
|
|
198
199
|
private meaningsResource?;
|
|
200
|
+
private messagingChannelsResource?;
|
|
199
201
|
private analysesResource?;
|
|
200
202
|
private mlTasksResource?;
|
|
201
203
|
private savedModelsResource?;
|
|
@@ -242,6 +244,7 @@ export declare class DataikuClient {
|
|
|
242
244
|
get workspaces(): WorkspacesResource;
|
|
243
245
|
get metrics(): MetricsResource;
|
|
244
246
|
get meanings(): MeaningsResource;
|
|
247
|
+
get messagingChannels(): MessagingChannelsResource;
|
|
245
248
|
get analyses(): AnalysesResource;
|
|
246
249
|
get mlTasks(): MlTasksResource;
|
|
247
250
|
get savedModels(): SavedModelsResource;
|
|
@@ -301,7 +304,7 @@ export declare class DataikuClient {
|
|
|
301
304
|
* the tail is kept and `truncated` is true — so callers that need the end
|
|
302
305
|
* of a large document (e.g. job logs) stay bounded without losing the most
|
|
303
306
|
* recent content. The request timeout applies per received chunk (a stall
|
|
304
|
-
* deadline)
|
|
307
|
+
* deadline); the whole transfer is capped at 10x the request timeout.
|
|
305
308
|
*/
|
|
306
309
|
getTextTailLimited(path: string, maxBytes: number): Promise<{
|
|
307
310
|
text: string;
|
|
@@ -346,6 +349,14 @@ export declare class DataikuClient {
|
|
|
346
349
|
* boundary is set correctly.
|
|
347
350
|
*/
|
|
348
351
|
uploadForm<T = unknown>(path: string, parts: UploadFormPart[], query?: URLSearchParams): Promise<T>;
|
|
352
|
+
/**
|
|
353
|
+
* POST a multipart/form-data body like {@link uploadForm} for endpoints whose
|
|
354
|
+
* success carries no JSON the caller needs (e.g. mail sends): the response
|
|
355
|
+
* body is not parsed, so a non-JSON acknowledgement cannot turn an already
|
|
356
|
+
* applied write into a client error.
|
|
357
|
+
*/
|
|
358
|
+
uploadFormVoid(path: string, parts: UploadFormPart[], query?: URLSearchParams): Promise<void>;
|
|
359
|
+
private postFormData;
|
|
349
360
|
private uploadResponse;
|
|
350
361
|
upload(path: string, filePath: string, fileName?: string): Promise<void>;
|
|
351
362
|
uploadJson<T>(path: string, filePath: string, fileName?: string): Promise<T>;
|
|
@@ -394,9 +405,10 @@ export declare class DataikuClient {
|
|
|
394
405
|
* decoded text never contains a replacement character.
|
|
395
406
|
*
|
|
396
407
|
* The request timeout applies per chunk (a stall deadline), like the
|
|
397
|
-
* streaming path: a
|
|
398
|
-
*
|
|
399
|
-
*
|
|
408
|
+
* streaming path: a body that stops sending for longer than the timeout
|
|
409
|
+
* fails with the body-read timeout error. A total budget of
|
|
410
|
+
* {@link TAIL_READ_TOTAL_BUDGET_FACTOR} x the request timeout bounds a body
|
|
411
|
+
* that keeps trickling, with an error naming that limit.
|
|
400
412
|
*/
|
|
401
413
|
private readBodyTextTail;
|
|
402
414
|
private buildResponseTooLargeError;
|