dataiku-sdk 3.9.0 → 4.0.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 +3 -3
- package/dist/build-metadata.json +1 -1
- package/dist/packages/types/src/index.d.ts +4 -0
- package/dist/packages/types/src/index.js +4 -0
- package/dist/src/cli/coerce.d.ts +6 -0
- package/dist/src/cli/coerce.js +9 -0
- package/dist/src/cli/commands/app.js +2 -2
- package/dist/src/cli/commands/code-env.js +5 -6
- package/dist/src/cli/commands/code.js +16 -2
- package/dist/src/cli/commands/dashboard.js +1 -2
- package/dist/src/cli/commands/flow-zone.js +1 -1
- package/dist/src/cli/commands/job.js +67 -16
- package/dist/src/cli/commands/plugin.js +7 -7
- package/dist/src/cli/commands/project-library.js +2 -2
- package/dist/src/cli/commands/project.js +21 -4
- package/dist/src/cli/commands/recipe.js +11 -19
- package/dist/src/cli/commands/scenario.js +2 -4
- package/dist/src/cli/contract.js +27 -5
- package/dist/src/cli/env.js +2 -3
- package/dist/src/cli/helpers/app-permissions.d.ts +1 -1
- package/dist/src/cli/helpers/app-permissions.js +2 -2
- package/dist/src/cli/helpers/cleanup.d.ts +2 -0
- package/dist/src/cli/helpers/cleanup.js +122 -8
- package/dist/src/cli/helpers/flow-zone.d.ts +1 -1
- package/dist/src/cli/helpers/flow-zone.js +3 -4
- package/dist/src/cli/list-output.d.ts +5 -0
- package/dist/src/cli/list-output.js +11 -2
- package/dist/src/cli/list-shapes.d.ts +6 -0
- package/dist/src/cli/list-shapes.js +7 -1
- package/dist/src/cli/output-schemas.d.ts +1 -1
- package/dist/src/cli/output-schemas.js +1 -1
- package/dist/src/cli/output.js +7 -0
- package/dist/src/cli/version.d.ts +2 -2
- package/dist/src/cli/version.js +4 -5
- package/dist/src/cli.js +66 -17
- package/dist/src/client.d.ts +14 -2
- package/dist/src/client.js +18 -10
- package/dist/src/generated/action-output-schemas.json +18 -0
- package/dist/src/index.d.ts +1 -1
- package/dist/src/resources/datasets.d.ts +11 -1
- package/dist/src/resources/datasets.js +91 -30
- package/dist/src/resources/jobs.d.ts +35 -5
- package/dist/src/resources/jobs.js +68 -41
- package/dist/src/resources/projects.js +2 -2
- package/dist/src/resources/recipes.d.ts +12 -1
- package/dist/src/resources/recipes.js +39 -12
- package/dist/src/resources/scenarios.d.ts +8 -1
- package/dist/src/resources/scenarios.js +51 -12
- package/dist/src/resources/wiki.js +3 -7
- package/dist/src/skill.js +1 -2
- package/dist/src/utils/project-archive.d.ts +25 -0
- package/dist/src/utils/project-archive.js +216 -5
- package/dist/src/utils/response-file.js +15 -8
- package/package.json +11 -12
- package/plugin.json +1 -1
- package/skills/dataiku-dss/SKILL.md +1 -1
|
@@ -188,7 +188,7 @@ export class ProjectsResource extends BaseResource {
|
|
|
188
188
|
&& settings.targetProjectKey.trim() !== ""
|
|
189
189
|
? { targetProjectKey: settings.targetProjectKey, }
|
|
190
190
|
: {}),
|
|
191
|
-
});
|
|
191
|
+
}, { cause: error, });
|
|
192
192
|
}
|
|
193
193
|
throw error;
|
|
194
194
|
}
|
|
@@ -246,7 +246,7 @@ export class ProjectsResource extends BaseResource {
|
|
|
246
246
|
catch (error) {
|
|
247
247
|
if (error instanceof DataikuError
|
|
248
248
|
&& (error.status === 404 || error.status === 0 || error.status >= 500)) {
|
|
249
|
-
throw new ClientValidationError(`DSS reported import success for project ${usedProjectKey}, but the project could not be re-read for verification.`, "ambiguous_outcome", PROJECT_IMPORT_AMBIGUOUS_REMEDIATION, { importId, usedProjectKey, });
|
|
249
|
+
throw new ClientValidationError(`DSS reported import success for project ${usedProjectKey}, but the project could not be re-read for verification.`, "ambiguous_outcome", PROJECT_IMPORT_AMBIGUOUS_REMEDIATION, { importId, usedProjectKey, }, { cause: error, });
|
|
250
250
|
}
|
|
251
251
|
throw error;
|
|
252
252
|
}
|
|
@@ -65,6 +65,14 @@ export interface RecipeCloneResult {
|
|
|
65
65
|
payloadTextRewrites: Record<string, string>;
|
|
66
66
|
copiedOutputDatasets: string[];
|
|
67
67
|
}
|
|
68
|
+
/**
|
|
69
|
+
* Validate an update patch against the current recipe document and return the
|
|
70
|
+
* document to PUT. `recipe` is deep-merged, except `recipe.params.envSelection`,
|
|
71
|
+
* which the patch replaces wholesale so no stale `envName` survives an
|
|
72
|
+
* EXPLICIT_ENV to INHERIT switch. `envSelection` outside `recipe.params` is
|
|
73
|
+
* rejected: neither DSS nor the CLI reads it there.
|
|
74
|
+
*/
|
|
75
|
+
export declare function buildRecipeUpdate(current: Record<string, unknown>, data: Record<string, unknown>): Record<string, unknown>;
|
|
68
76
|
export declare class RecipesResource extends BaseResource {
|
|
69
77
|
/** List all recipes in a project. */
|
|
70
78
|
list(projectKey?: string): Promise<RecipeSummary[]>;
|
|
@@ -136,7 +144,10 @@ export declare class RecipesResource extends BaseResource {
|
|
|
136
144
|
}): Promise<RecipeUpdateSchemaResult>;
|
|
137
145
|
/**
|
|
138
146
|
* Update a recipe by merging the patch into the current definition.
|
|
139
|
-
* The `recipe` sub-object is deep-merged to preserve nested fields
|
|
147
|
+
* The `recipe` sub-object is deep-merged to preserve nested fields, except
|
|
148
|
+
* `recipe.params.envSelection`: when the patch provides it, the whole object
|
|
149
|
+
* is replaced (not merged), so switching EXPLICIT_ENV to INHERIT cannot leave
|
|
150
|
+
* a stale `envName` behind. `envSelection` outside `recipe.params` is rejected.
|
|
140
151
|
*/
|
|
141
152
|
update(recipeName: string, data: Record<string, unknown>, projectKey?: string): Promise<void>;
|
|
142
153
|
/** Replace a full recipe API document. */
|
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { writeFile, } from "node:fs/promises";
|
|
2
1
|
import { resolve, } from "node:path";
|
|
3
2
|
import { ClientValidationError, DataikuError, nonJsonResponseBody, unexpectedResponseError, } from "../errors.js";
|
|
4
3
|
import { ProjectMetadataSchema, RecipeSchemaUpdatesSchema, RecipeSummaryArraySchema, } from "../schemas.js";
|
|
@@ -40,6 +39,37 @@ const RECIPE_DEFINITION_FIELDS = new Set(["params", "inputs", "outputs", "script
|
|
|
40
39
|
function rootRecipeDefinitionFields(data) {
|
|
41
40
|
return Object.keys(data).filter((key) => RECIPE_DEFINITION_FIELDS.has(key));
|
|
42
41
|
}
|
|
42
|
+
/**
|
|
43
|
+
* Validate an update patch against the current recipe document and return the
|
|
44
|
+
* document to PUT. `recipe` is deep-merged, except `recipe.params.envSelection`,
|
|
45
|
+
* which the patch replaces wholesale so no stale `envName` survives an
|
|
46
|
+
* EXPLICIT_ENV to INHERIT switch. `envSelection` outside `recipe.params` is
|
|
47
|
+
* rejected: neither DSS nor the CLI reads it there.
|
|
48
|
+
*/
|
|
49
|
+
export function buildRecipeUpdate(current, data) {
|
|
50
|
+
const currentRecipe = asRecord(current.recipe) ?? {};
|
|
51
|
+
const recipePatch = asRecord(data.recipe) ?? {};
|
|
52
|
+
const misplacedEnvSelection = [
|
|
53
|
+
...("envSelection" in data ? ["envSelection",] : []),
|
|
54
|
+
...("envSelection" in recipePatch ? ["recipe.envSelection",] : []),
|
|
55
|
+
];
|
|
56
|
+
if (misplacedEnvSelection.length > 0) {
|
|
57
|
+
throw new ClientValidationError(`envSelection at ${misplacedEnvSelection.join(" and ")} is ignored; it only applies under recipe.params. Example: {"recipe":{"params":{"envSelection":{"envMode":"EXPLICIT_ENV","envName":"py311_env"}}}}`, "validation_failed");
|
|
58
|
+
}
|
|
59
|
+
const misplacedRecipeFields = rootRecipeDefinitionFields(data);
|
|
60
|
+
if (misplacedRecipeFields.length > 0) {
|
|
61
|
+
throw new ClientValidationError(`Recipe fields ${misplacedRecipeFields.join(", ")} must be nested under "recipe". Example: {"recipe":{"outputs":{...},"params":{...}}}`, "validation_failed");
|
|
62
|
+
}
|
|
63
|
+
const mergedRecipe = deepMerge(currentRecipe, recipePatch);
|
|
64
|
+
const paramsPatch = asRecord(recipePatch.params);
|
|
65
|
+
if (paramsPatch && "envSelection" in paramsPatch) {
|
|
66
|
+
mergedRecipe.params = {
|
|
67
|
+
...asRecord(mergedRecipe.params),
|
|
68
|
+
envSelection: paramsPatch.envSelection,
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
return { ...current, ...data, recipe: mergedRecipe, };
|
|
72
|
+
}
|
|
43
73
|
function normalizeRecipeOutputType(value) {
|
|
44
74
|
if (typeof value !== "string")
|
|
45
75
|
return undefined;
|
|
@@ -823,22 +853,19 @@ export class RecipesResource extends BaseResource {
|
|
|
823
853
|
}
|
|
824
854
|
/**
|
|
825
855
|
* Update a recipe by merging the patch into the current definition.
|
|
826
|
-
* The `recipe` sub-object is deep-merged to preserve nested fields
|
|
856
|
+
* The `recipe` sub-object is deep-merged to preserve nested fields, except
|
|
857
|
+
* `recipe.params.envSelection`: when the patch provides it, the whole object
|
|
858
|
+
* is replaced (not merged), so switching EXPLICIT_ENV to INHERIT cannot leave
|
|
859
|
+
* a stale `envName` behind. `envSelection` outside `recipe.params` is rejected.
|
|
827
860
|
*/
|
|
828
861
|
async update(recipeName, data, projectKey) {
|
|
829
862
|
const enc = this.enc(projectKey);
|
|
830
863
|
const rnEnc = encodeURIComponent(recipeName);
|
|
831
864
|
const current = await this.client.get(`/public/api/projects/${enc}/recipes/${rnEnc}`);
|
|
832
|
-
|
|
833
|
-
if (!currentRecipe) {
|
|
865
|
+
if (!asRecord(current.recipe)) {
|
|
834
866
|
throw new ClientValidationError(`Recipe "${recipeName}" was not found or returned an empty definition.`, "validation_failed");
|
|
835
867
|
}
|
|
836
|
-
const
|
|
837
|
-
if (misplacedRecipeFields.length > 0) {
|
|
838
|
-
throw new ClientValidationError(`Recipe fields ${misplacedRecipeFields.join(", ")} must be nested under "recipe". Example: {"recipe":{"outputs":{...},"params":{...}}}`, "validation_failed");
|
|
839
|
-
}
|
|
840
|
-
const mergedRecipe = deepMerge(currentRecipe, asRecord(data.recipe) ?? {});
|
|
841
|
-
const merged = { ...current, ...data, recipe: mergedRecipe, };
|
|
868
|
+
const merged = buildRecipeUpdate(current, data);
|
|
842
869
|
await this.client.put(`/public/api/projects/${enc}/recipes/${rnEnc}`, merged);
|
|
843
870
|
}
|
|
844
871
|
/** Replace a full recipe API document. */
|
|
@@ -922,7 +949,7 @@ export class RecipesResource extends BaseResource {
|
|
|
922
949
|
}
|
|
923
950
|
const safeRecipeName = sanitizeFileName(recipeName, "recipe");
|
|
924
951
|
const filePath = opts?.outputPath ?? resolve(process.cwd(), `${safeRecipeName}${inferRecipeCodeExtension(result.recipe.type)}`);
|
|
925
|
-
await
|
|
952
|
+
await Bun.write(filePath, result.payload);
|
|
926
953
|
return filePath;
|
|
927
954
|
}
|
|
928
955
|
/** Get only the code payload of a recipe as a raw string. */
|
|
@@ -965,7 +992,7 @@ export class RecipesResource extends BaseResource {
|
|
|
965
992
|
const recipe = await this.client.get(`/public/api/projects/${enc}/recipes/${rnEnc}`);
|
|
966
993
|
const safeRecipeName = sanitizeFileName(recipeName, "recipe");
|
|
967
994
|
const filePath = opts?.outputPath ?? resolve(process.cwd(), `${safeRecipeName}.json`);
|
|
968
|
-
await
|
|
995
|
+
await Bun.write(filePath, JSON.stringify(recipe, null, 2));
|
|
969
996
|
return filePath;
|
|
970
997
|
}
|
|
971
998
|
}
|
|
@@ -107,6 +107,10 @@ export interface ScenarioScriptRunResult {
|
|
|
107
107
|
elapsedMs: number;
|
|
108
108
|
pollCount: number;
|
|
109
109
|
output?: string;
|
|
110
|
+
/** True when the captured script output was cut (wrapper byte budget or log read cap); `output` then holds the last part only. */
|
|
111
|
+
outputTruncated?: boolean;
|
|
112
|
+
/** Bytes of script output the wrapper dropped from the front; absent when the cut happened in the log read instead. */
|
|
113
|
+
outputOmittedBytes?: number;
|
|
110
114
|
log: string;
|
|
111
115
|
logTruncated: boolean;
|
|
112
116
|
maxLogBytes: number;
|
|
@@ -186,12 +190,15 @@ export declare class ScenariosResource extends BaseResource {
|
|
|
186
190
|
* Get the log of a scenario run (GET /scenarios/{id}/{runId}/log), optionally
|
|
187
191
|
* scoped to a single step via `stepId`. The text is byte-bounded: at most
|
|
188
192
|
* `maxLogBytes` (default 1 MiB) of the beginning of the log is retained and
|
|
189
|
-
* `truncated` reports whether the cap cut the body.
|
|
193
|
+
* `truncated` reports whether the cap cut the body. With `tail: true` the
|
|
194
|
+
* last `maxLogBytes` are retained instead (used by code runs, whose output
|
|
195
|
+
* markers sit at the end of the log).
|
|
190
196
|
*/
|
|
191
197
|
getRunLog(scenarioId: string, runId: string, opts?: {
|
|
192
198
|
stepId?: string;
|
|
193
199
|
maxLogBytes?: number;
|
|
194
200
|
projectKey?: string;
|
|
201
|
+
tail?: boolean;
|
|
195
202
|
}): Promise<ScenarioRunLog>;
|
|
196
203
|
/** Get the payload of a (custom) scenario (GET /scenarios/{id}/payload). */
|
|
197
204
|
getPayload(scenarioId: string, opts?: {
|
|
@@ -32,6 +32,14 @@ export class ScenarioScriptRunWithCleanupFailureError extends Error {
|
|
|
32
32
|
const DEFAULT_CODE_RUN_MAX_LOG_BYTES = 1_048_576;
|
|
33
33
|
const CODE_RUN_OUTPUT_START = "<<<DSS_CODE_RUN_OUTPUT_b7e3a1>>>";
|
|
34
34
|
const CODE_RUN_OUTPUT_END = "<<<DSS_CODE_RUN_OUTPUT_END_b7e3a1>>>";
|
|
35
|
+
/** Written right after the END marker when the wrapper cut the captured output; carries the omitted byte count. */
|
|
36
|
+
const CODE_RUN_OUTPUT_TRUNCATED_RE = /^<<<DSS_CODE_RUN_OUTPUT_TRUNCATED_b7e3a1:(\d+)>>>$/;
|
|
37
|
+
/**
|
|
38
|
+
* The wrapper keeps at most `maxLogBytes / CODE_RUN_OUTPUT_BUDGET_DIVISOR` bytes
|
|
39
|
+
* of script output. The remainder of the read cap is headroom for the per-line
|
|
40
|
+
* DSS log prefix, so the markers still fit in what `runScript` reads back.
|
|
41
|
+
*/
|
|
42
|
+
const CODE_RUN_OUTPUT_BUDGET_DIVISOR = 4;
|
|
35
43
|
/** Prefer the current runId field, with the legacy id field as fallback. */
|
|
36
44
|
function triggerRunIdOf(trigger, scenarioId) {
|
|
37
45
|
const runId = trigger?.runId ?? trigger?.id;
|
|
@@ -80,8 +88,10 @@ function scenarioLightStatusFromRaw(raw) {
|
|
|
80
88
|
* wrapper noise. The script is base64-encoded to avoid quoting/escaping issues and
|
|
81
89
|
* exec'd as `__main__`. A failing script re-raises SystemExit(1) so the scenario
|
|
82
90
|
* outcome is FAILED while the captured traceback still lands between the markers.
|
|
91
|
+
* Only the last `maxOutputBytes` UTF-8 bytes of output are re-emitted; when output
|
|
92
|
+
* was cut, a truncation marker with the omitted byte count follows the END marker.
|
|
83
93
|
*/
|
|
84
|
-
function buildCodeRunScript(script) {
|
|
94
|
+
function buildCodeRunScript(script, maxOutputBytes) {
|
|
85
95
|
const encoded = Buffer.from(script, "utf-8").toString("base64");
|
|
86
96
|
return [
|
|
87
97
|
"import base64 as _dku_b64, sys as _dku_sys, io as _dku_io, traceback as _dku_tb",
|
|
@@ -99,9 +109,14 @@ function buildCodeRunScript(script) {
|
|
|
99
109
|
"\t_dku_tb.print_exc()",
|
|
100
110
|
"finally:",
|
|
101
111
|
"\t_dku_sys.stdout, _dku_sys.stderr = _dku_out, _dku_err",
|
|
112
|
+
'\t_dku_bytes = _dku_buf.getvalue().encode("utf-8", "replace")',
|
|
113
|
+
`\t_dku_omitted = max(0, len(_dku_bytes) - ${maxOutputBytes})`,
|
|
114
|
+
'\t_dku_text = _dku_bytes[_dku_omitted:].decode("utf-8", "ignore")',
|
|
102
115
|
`\t_dku_out.write("${CODE_RUN_OUTPUT_START}\\n")`,
|
|
103
|
-
"\t_dku_out.write(
|
|
116
|
+
"\t_dku_out.write(_dku_text)",
|
|
104
117
|
`\t_dku_out.write("\\n${CODE_RUN_OUTPUT_END}\\n")`,
|
|
118
|
+
"\tif _dku_omitted:",
|
|
119
|
+
'\t\t_dku_out.write("<<<DSS_CODE_RUN_OUTPUT_TRUNCATED_b7e3a1:%d>>>\\n" % _dku_omitted)',
|
|
105
120
|
"\t_dku_out.flush()",
|
|
106
121
|
"if _dku_code:",
|
|
107
122
|
"\traise SystemExit(_dku_code)",
|
|
@@ -112,10 +127,13 @@ function buildCodeRunScript(script) {
|
|
|
112
127
|
* Pull the script's own stdout/stderr back out of the full DSS run log by slicing
|
|
113
128
|
* the `[process]` lines between the markers emitted by {@link buildCodeRunScript}.
|
|
114
129
|
* Returns undefined if the markers are absent (e.g. the harness never ran), in which
|
|
115
|
-
* case callers should fall back to the full log.
|
|
130
|
+
* case callers should fall back to the full log. When the log read itself was cut
|
|
131
|
+
* (`logTruncated`) the START marker may be gone while END survives; the output is
|
|
132
|
+
* then whatever precedes END, reported as truncated.
|
|
116
133
|
*/
|
|
117
|
-
function extractCodeRunOutput(log) {
|
|
118
|
-
|
|
134
|
+
function extractCodeRunOutput(log, logTruncated) {
|
|
135
|
+
// `[\s\S]` (not `.`): CR, U+2028 and U+2029 inside a printed line must not drop it.
|
|
136
|
+
const messageRe = /^\[[^\]]*\] \[[^\]]*\] \[[^\]]*\] \[process\] - ([\s\S]*)$/;
|
|
119
137
|
const contents = [];
|
|
120
138
|
for (const rawLine of log.split("\n")) {
|
|
121
139
|
const line = rawLine.endsWith("\r") ? rawLine.slice(0, -1) : rawLine;
|
|
@@ -125,7 +143,7 @@ function extractCodeRunOutput(log) {
|
|
|
125
143
|
}
|
|
126
144
|
// First start + last end, so a script that prints a marker string stays body content.
|
|
127
145
|
const start = contents.indexOf(CODE_RUN_OUTPUT_START);
|
|
128
|
-
if (start < 0)
|
|
146
|
+
if (start < 0 && !logTruncated)
|
|
129
147
|
return undefined;
|
|
130
148
|
let end = -1;
|
|
131
149
|
for (let i = contents.length - 1; i > start; i--) {
|
|
@@ -140,7 +158,13 @@ function extractCodeRunOutput(log) {
|
|
|
140
158
|
// Drop only the single trailing separator the harness writes before the end marker.
|
|
141
159
|
if (body.length > 0 && body[body.length - 1] === "")
|
|
142
160
|
body.pop();
|
|
143
|
-
|
|
161
|
+
const omitted = CODE_RUN_OUTPUT_TRUNCATED_RE.exec(contents[end + 1] ?? "");
|
|
162
|
+
const omittedBytes = omitted ? Number(omitted[1]) : undefined;
|
|
163
|
+
return {
|
|
164
|
+
output: body.join("\n"),
|
|
165
|
+
truncated: omitted !== null || start < 0,
|
|
166
|
+
...(omittedBytes !== undefined ? { omittedBytes, } : {}),
|
|
167
|
+
};
|
|
144
168
|
}
|
|
145
169
|
function scenarioStepWarningSummary(stepResult) {
|
|
146
170
|
const container = stepResult?.warnings;
|
|
@@ -441,7 +465,10 @@ export class ScenariosResource extends BaseResource {
|
|
|
441
465
|
type: "custom_python",
|
|
442
466
|
params: { envSelection, },
|
|
443
467
|
});
|
|
444
|
-
await this.client.putVoid(`${base}/payload`, {
|
|
468
|
+
await this.client.putVoid(`${base}/payload`, {
|
|
469
|
+
script: buildCodeRunScript(script, Math.floor(maxLogBytes / CODE_RUN_OUTPUT_BUDGET_DIVISOR)),
|
|
470
|
+
extension: "py",
|
|
471
|
+
});
|
|
445
472
|
const trigger = await this.client.post(`${base}/run/`, {});
|
|
446
473
|
const tracked = await this.trackTriggerRun(scenarioId, base, trigger, {
|
|
447
474
|
startedAt,
|
|
@@ -454,9 +481,12 @@ export class ScenariosResource extends BaseResource {
|
|
|
454
481
|
pollCount = tracked.pollCount;
|
|
455
482
|
timedOut = tracked.timedOut;
|
|
456
483
|
if (runId && outcome !== "TIMEOUT") {
|
|
484
|
+
// Tail read: the markers are printed last, so a log over the cap loses
|
|
485
|
+
// DSS start-up noise instead of the END marker.
|
|
457
486
|
const limitedLog = await this.getRunLog(scenarioId, runId, {
|
|
458
487
|
projectKey: opts?.projectKey,
|
|
459
488
|
maxLogBytes,
|
|
489
|
+
tail: true,
|
|
460
490
|
});
|
|
461
491
|
log = limitedLog.text;
|
|
462
492
|
logTruncated = limitedLog.truncated;
|
|
@@ -475,7 +505,7 @@ export class ScenariosResource extends BaseResource {
|
|
|
475
505
|
}
|
|
476
506
|
throw primaryError;
|
|
477
507
|
}
|
|
478
|
-
const
|
|
508
|
+
const extracted = extractCodeRunOutput(log, logTruncated);
|
|
479
509
|
return {
|
|
480
510
|
scenarioId,
|
|
481
511
|
runId,
|
|
@@ -483,7 +513,11 @@ export class ScenariosResource extends BaseResource {
|
|
|
483
513
|
success: outcome === "SUCCESS",
|
|
484
514
|
elapsedMs: Date.now() - startedAt,
|
|
485
515
|
pollCount,
|
|
486
|
-
output,
|
|
516
|
+
output: extracted?.output,
|
|
517
|
+
...(extracted?.truncated ? { outputTruncated: true, } : {}),
|
|
518
|
+
...(extracted?.omittedBytes !== undefined
|
|
519
|
+
? { outputOmittedBytes: extracted.omittedBytes, }
|
|
520
|
+
: {}),
|
|
487
521
|
log,
|
|
488
522
|
logTruncated,
|
|
489
523
|
maxLogBytes,
|
|
@@ -519,7 +553,9 @@ export class ScenariosResource extends BaseResource {
|
|
|
519
553
|
* Get the log of a scenario run (GET /scenarios/{id}/{runId}/log), optionally
|
|
520
554
|
* scoped to a single step via `stepId`. The text is byte-bounded: at most
|
|
521
555
|
* `maxLogBytes` (default 1 MiB) of the beginning of the log is retained and
|
|
522
|
-
* `truncated` reports whether the cap cut the body.
|
|
556
|
+
* `truncated` reports whether the cap cut the body. With `tail: true` the
|
|
557
|
+
* last `maxLogBytes` are retained instead (used by code runs, whose output
|
|
558
|
+
* markers sit at the end of the log).
|
|
523
559
|
*/
|
|
524
560
|
async getRunLog(scenarioId, runId, opts) {
|
|
525
561
|
if (opts?.stepId !== undefined)
|
|
@@ -529,7 +565,10 @@ export class ScenariosResource extends BaseResource {
|
|
|
529
565
|
const query = opts?.stepId !== undefined
|
|
530
566
|
? `?stepId=${encodeURIComponent(opts.stepId)}`
|
|
531
567
|
: "";
|
|
532
|
-
const
|
|
568
|
+
const readText = opts?.tail === true
|
|
569
|
+
? this.client.getTextTailLimited.bind(this.client)
|
|
570
|
+
: this.client.getTextLimited.bind(this.client);
|
|
571
|
+
const { text, truncated, } = await readText(`/public/api/projects/${this.enc(opts?.projectKey)}/scenarios/${scEnc}/${encodeURIComponent(runId)}/log${query}`, maxLogBytes);
|
|
533
572
|
return { text, truncated, maxLogBytes, };
|
|
534
573
|
}
|
|
535
574
|
/** Get the payload of a (custom) scenario (GET /scenarios/{id}/payload). */
|
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import
|
|
1
|
+
import { stat as statFile, } from "node:fs/promises";
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import { ClientValidationError, } from "../errors.js";
|
|
4
4
|
import { WikiArticleDataArraySchema, WikiArticleDataSchema, WikiSettingsSchema, } from "../schemas.js";
|
|
@@ -19,13 +19,9 @@ export function wikiAttachmentNotFound(data, smartId) {
|
|
|
19
19
|
}
|
|
20
20
|
/** Check that a local path is a readable regular file before any DSS call. */
|
|
21
21
|
export async function statWikiAttachmentFile(localPath) {
|
|
22
|
-
|
|
23
|
-
try {
|
|
24
|
-
stat = await fs.promises.stat(localPath);
|
|
25
|
-
}
|
|
26
|
-
catch (error) {
|
|
22
|
+
const stat = await statFile(localPath).catch((error) => {
|
|
27
23
|
throw new ClientValidationError(`Could not read attachment file ${localPath}: ${error instanceof Error ? error.message : String(error)}.`, "not_found", "Verify the file path and that it is readable.");
|
|
28
|
-
}
|
|
24
|
+
});
|
|
29
25
|
if (!stat.isFile()) {
|
|
30
26
|
throw new ClientValidationError(`${localPath} is not a regular file.`);
|
|
31
27
|
}
|
package/dist/src/skill.js
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import * as fs from "node:fs";
|
|
2
2
|
import * as os from "node:os";
|
|
3
3
|
import * as path from "node:path";
|
|
4
|
-
import { fileURLToPath, } from "node:url";
|
|
5
4
|
import { ClientValidationError, } from "./errors.js";
|
|
6
5
|
import { sha256Hex, } from "./utils/stable-hash.js";
|
|
7
6
|
const SKILL_URLS = [
|
|
@@ -114,7 +113,7 @@ function skillState(target, expectedSha256) {
|
|
|
114
113
|
}
|
|
115
114
|
/** Load the packaged tree, keeping portable relative paths in the report. */
|
|
116
115
|
function skillFiles() {
|
|
117
|
-
const root = fileURLToPath(skillDirectory());
|
|
116
|
+
const root = Bun.fileURLToPath(skillDirectory());
|
|
118
117
|
const files = new Map();
|
|
119
118
|
function visit(relativeDir) {
|
|
120
119
|
const dir = path.join(root, relativeDir);
|
|
@@ -35,6 +35,25 @@ export interface ProjectArchiveIssue {
|
|
|
35
35
|
message: string;
|
|
36
36
|
member?: string;
|
|
37
37
|
}
|
|
38
|
+
/**
|
|
39
|
+
* One credential-bearing structure found in an archive member. Carries the
|
|
40
|
+
* member path and the JSON key path / field name only, never the value.
|
|
41
|
+
* - `member_name`: the member itself is a credential store (for example
|
|
42
|
+
* `project_config/apikeys.json`) and is not empty.
|
|
43
|
+
* - `json_key`: a credential-named JSON key holds a non-empty string value
|
|
44
|
+
* (or a container of them), e.g. `$.standard.DHL_API_KEY` or a nested
|
|
45
|
+
* web-app `apiKey`.
|
|
46
|
+
* - `list_variable`: a list-form `{name, value}` entry whose `name` is
|
|
47
|
+
* credential-named, e.g. `{name: "X_API_KEY", value: "..."}`.
|
|
48
|
+
*/
|
|
49
|
+
export interface ProjectArchiveCredentialFinding {
|
|
50
|
+
member: string;
|
|
51
|
+
rule: "member_name" | "json_key" | "list_variable";
|
|
52
|
+
/** JSON path of the credential value inside the member (absent for `member_name`). */
|
|
53
|
+
keyPath?: string;
|
|
54
|
+
/** Credential-named key or list-form variable name (absent for `member_name`). */
|
|
55
|
+
field?: string;
|
|
56
|
+
}
|
|
38
57
|
export interface ProjectArchiveInspection {
|
|
39
58
|
filePath: string;
|
|
40
59
|
sizeBytes: number;
|
|
@@ -42,6 +61,12 @@ export interface ProjectArchiveInspection {
|
|
|
42
61
|
sourceProjectKey?: string;
|
|
43
62
|
valid: boolean;
|
|
44
63
|
issues: ProjectArchiveIssue[];
|
|
64
|
+
/**
|
|
65
|
+
* Credential inventory: member paths and key paths/field names of
|
|
66
|
+
* credential-bearing structures. Values are never included. Each finding
|
|
67
|
+
* is also summarized by a `credential_structure` warning issue.
|
|
68
|
+
*/
|
|
69
|
+
credentialFindings: ProjectArchiveCredentialFinding[];
|
|
45
70
|
}
|
|
46
71
|
/**
|
|
47
72
|
* Inspect a DSS project export ZIP archive without extracting anything to
|