dataiku-sdk 3.2.0 → 3.4.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 (132) hide show
  1. package/README.md +151 -46
  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/cli/commands/app.js +57 -15
  6. package/dist/src/cli/commands/bundle.js +1 -1
  7. package/dist/src/cli/commands/code-env.js +13 -7
  8. package/dist/src/cli/commands/code.js +1 -1
  9. package/dist/src/cli/commands/connection.d.ts +9 -0
  10. package/dist/src/cli/commands/connection.js +267 -1
  11. package/dist/src/cli/commands/data-collection.d.ts +2 -0
  12. package/dist/src/cli/commands/data-collection.js +164 -0
  13. package/dist/src/cli/commands/dataset.js +162 -33
  14. package/dist/src/cli/commands/future.js +2 -2
  15. package/dist/src/cli/commands/group.d.ts +2 -0
  16. package/dist/src/cli/commands/group.js +109 -0
  17. package/dist/src/cli/commands/index.js +151 -78
  18. package/dist/src/cli/commands/job.js +4 -2
  19. package/dist/src/cli/commands/knowledge-bank.d.ts +2 -0
  20. package/dist/src/cli/commands/knowledge-bank.js +80 -0
  21. package/dist/src/cli/commands/llm.d.ts +2 -0
  22. package/dist/src/cli/commands/llm.js +93 -0
  23. package/dist/src/cli/commands/macro.d.ts +2 -0
  24. package/dist/src/cli/commands/macro.js +221 -0
  25. package/dist/src/cli/commands/ml-task.js +18 -6
  26. package/dist/src/cli/commands/notebook.js +6 -2
  27. package/dist/src/cli/commands/plugin.d.ts +2 -0
  28. package/dist/src/cli/commands/plugin.js +799 -0
  29. package/dist/src/cli/commands/project-folder.d.ts +2 -0
  30. package/dist/src/cli/commands/project-folder.js +197 -0
  31. package/dist/src/cli/commands/project-git.js +15 -55
  32. package/dist/src/cli/commands/project.js +52 -6
  33. package/dist/src/cli/commands/recipe.js +36 -1
  34. package/dist/src/cli/commands/saved-model.d.ts +5 -0
  35. package/dist/src/cli/commands/saved-model.js +388 -1
  36. package/dist/src/cli/commands/scenario.js +139 -2
  37. package/dist/src/cli/commands/user.d.ts +8 -0
  38. package/dist/src/cli/commands/user.js +296 -0
  39. package/dist/src/cli/contract.d.ts +1 -2
  40. package/dist/src/cli/contract.js +1098 -297
  41. package/dist/src/cli/flags.js +30 -1
  42. package/dist/src/cli/helpers/app-successor.d.ts +19 -4
  43. package/dist/src/cli/helpers/app-successor.js +90 -30
  44. package/dist/src/cli/helpers/cleanup.js +3 -1
  45. package/dist/src/cli/helpers/flow-zone.js +1 -2
  46. package/dist/src/cli/output-schemas.d.ts +24 -0
  47. package/dist/src/cli/output-schemas.js +233 -0
  48. package/dist/src/cli/output.d.ts +1 -1
  49. package/dist/src/cli/output.js +23 -10
  50. package/dist/src/cli/usage.d.ts +1 -0
  51. package/dist/src/cli/usage.js +47 -1
  52. package/dist/src/cli.js +147 -41
  53. package/dist/src/client.d.ts +92 -5
  54. package/dist/src/client.js +202 -31
  55. package/dist/src/errors.d.ts +3 -0
  56. package/dist/src/errors.js +8 -4
  57. package/dist/src/generated/action-output-schemas.json +1735 -0
  58. package/dist/src/index.d.ts +16 -7
  59. package/dist/src/index.js +11 -2
  60. package/dist/src/resources/analyses.js +1 -8
  61. package/dist/src/resources/applications.d.ts +8 -6
  62. package/dist/src/resources/applications.js +21 -21
  63. package/dist/src/resources/base.d.ts +4 -0
  64. package/dist/src/resources/base.js +15 -0
  65. package/dist/src/resources/bundles.d.ts +9 -4
  66. package/dist/src/resources/bundles.js +10 -5
  67. package/dist/src/resources/connections.d.ts +78 -1
  68. package/dist/src/resources/connections.js +132 -1
  69. package/dist/src/resources/data-collections.d.ts +118 -0
  70. package/dist/src/resources/data-collections.js +49 -0
  71. package/dist/src/resources/datasets.d.ts +69 -0
  72. package/dist/src/resources/datasets.js +136 -5
  73. package/dist/src/resources/flow-zones.d.ts +6 -2
  74. package/dist/src/resources/flow-zones.js +6 -3
  75. package/dist/src/resources/futures.d.ts +6 -1
  76. package/dist/src/resources/futures.js +68 -22
  77. package/dist/src/resources/groups.d.ts +47 -0
  78. package/dist/src/resources/groups.js +56 -0
  79. package/dist/src/resources/jobs.d.ts +8 -13
  80. package/dist/src/resources/jobs.js +71 -34
  81. package/dist/src/resources/knowledge-banks.d.ts +58 -0
  82. package/dist/src/resources/knowledge-banks.js +61 -0
  83. package/dist/src/resources/llms.d.ts +92 -0
  84. package/dist/src/resources/llms.js +62 -0
  85. package/dist/src/resources/macros.d.ts +121 -0
  86. package/dist/src/resources/macros.js +231 -0
  87. package/dist/src/resources/ml-tasks.d.ts +37 -2
  88. package/dist/src/resources/ml-tasks.js +109 -20
  89. package/dist/src/resources/model-evaluation-stores.js +1 -8
  90. package/dist/src/resources/notebooks.d.ts +43 -7
  91. package/dist/src/resources/notebooks.js +53 -13
  92. package/dist/src/resources/plugins.d.ts +263 -0
  93. package/dist/src/resources/plugins.js +455 -0
  94. package/dist/src/resources/project-folders.d.ts +71 -0
  95. package/dist/src/resources/project-folders.js +57 -0
  96. package/dist/src/resources/project-git.d.ts +12 -12
  97. package/dist/src/resources/project-git.js +84 -82
  98. package/dist/src/resources/project-library.d.ts +0 -1
  99. package/dist/src/resources/project-library.js +1 -17
  100. package/dist/src/resources/projects.d.ts +23 -3
  101. package/dist/src/resources/projects.js +60 -34
  102. package/dist/src/resources/recipes.d.ts +15 -1
  103. package/dist/src/resources/recipes.js +73 -6
  104. package/dist/src/resources/saved-models.d.ts +134 -1
  105. package/dist/src/resources/saved-models.js +214 -6
  106. package/dist/src/resources/scenarios.d.ts +105 -0
  107. package/dist/src/resources/scenarios.js +232 -106
  108. package/dist/src/resources/sql.js +5 -1
  109. package/dist/src/resources/users.d.ts +129 -0
  110. package/dist/src/resources/users.js +128 -0
  111. package/dist/src/resources/webapps.d.ts +15 -2
  112. package/dist/src/resources/webapps.js +18 -4
  113. package/dist/src/schemas.d.ts +2 -2
  114. package/dist/src/schemas.js +1 -1
  115. package/dist/src/utils/deep-merge.d.ts +1 -1
  116. package/dist/src/utils/git-reference.d.ts +12 -0
  117. package/dist/src/utils/git-reference.js +38 -0
  118. package/dist/src/utils/polling.d.ts +20 -0
  119. package/dist/src/utils/polling.js +27 -0
  120. package/dist/src/utils/response-file.js +1 -1
  121. package/dist/src/utils/secret-sanitize.d.ts +54 -0
  122. package/dist/src/utils/secret-sanitize.js +101 -0
  123. package/package.json +9 -8
  124. package/packages/types/dist/index.d.ts +102 -0
  125. package/packages/types/dist/index.js +76 -0
  126. package/plugin.json +1 -1
  127. package/skills/dataiku-dss/SKILL.md +11 -7
  128. package/skills/dataiku-dss/references/app-releases.md +8 -8
  129. package/skills/dataiku-dss/references/authentication.md +6 -6
  130. package/skills/dataiku-dss/references/discovery.md +4 -4
  131. package/skills/dataiku-dss/references/mutations.md +1 -1
  132. package/skills/dataiku-dss/references/troubleshooting.md +2 -2
@@ -0,0 +1,121 @@
1
+ import type { MacroDefinition, MacroRunStart, MacroState, MacroSummary, MacroWaitResult } from "../schemas.js";
2
+ import { BaseResource } from "./base.js";
3
+ export interface MacroRunOptions {
4
+ /**
5
+ * Server-side wait: when true the POST call blocks until the run finishes
6
+ * (docs: `POST /runnables/{runnableType}?wait=true`). Default false.
7
+ */
8
+ wait?: boolean;
9
+ projectKey?: string;
10
+ /**
11
+ * Macro parameter values keyed by the param names declared in the macro
12
+ * definition (`params` array). Sent as the POST body. Values must be
13
+ * JSON-serializable primitives or structures matching the declared types.
14
+ */
15
+ params?: Record<string, unknown>;
16
+ /**
17
+ * Admin-only parameter values keyed by the adminParams names declared in
18
+ * the macro definition. Sent as the POST body's adminParams entry.
19
+ */
20
+ adminParams?: Record<string, unknown>;
21
+ }
22
+ export interface MacroWaitOptions {
23
+ pollIntervalMs?: number;
24
+ timeoutMs?: number;
25
+ projectKey?: string;
26
+ /** Macro parameter values (see {@link MacroRunOptions.params}). */
27
+ params?: Record<string, unknown>;
28
+ /** Admin-only parameter values (see {@link MacroRunOptions.adminParams}). */
29
+ adminParams?: Record<string, unknown>;
30
+ }
31
+ export interface MacroResultOptions {
32
+ projectKey?: string;
33
+ /**
34
+ * Cap on the number of bytes buffered from the result body. Macro results
35
+ * can be arbitrary documents (HTML, text, JSON); the default 8 MiB guards
36
+ * against oversized bodies while remaining generous for typical reports.
37
+ */
38
+ maxBytes?: number;
39
+ }
40
+ /**
41
+ * Normalize a byte cap coming from user input (CLI `--max-bytes`) into the
42
+ * inclusive range [1, 2 GiB], rejecting non-integers before any DSS request.
43
+ */
44
+ export declare function macroResultMaxBytes(maxBytes: number | undefined): number;
45
+ declare function isRecord(value: unknown): value is Record<string, unknown>;
46
+ /**
47
+ * Run result of {@link MacrosResource.run} when the caller opted into
48
+ * `wait: false` (the default): a run id for abort/state/result tracking.
49
+ */
50
+ export type MacroRunHandle = MacroRunStart;
51
+ /**
52
+ * When `wait: true`, DSS blocks server-side until the run finishes and then
53
+ * returns the run id plus the terminal poll state (resultType, errors, …).
54
+ */
55
+ export type MacroRunWaitedResult = MacroRunStart & {
56
+ state?: MacroState;
57
+ };
58
+ /**
59
+ * Error payload surfaced by DSS on a failed macro run, from either
60
+ * `resultError` or `storedError` in the poll state.
61
+ */
62
+ export interface MacroRunFailure {
63
+ source: "resultError" | "storedError";
64
+ details: unknown;
65
+ }
66
+ declare function macroRunFailure(state: Record<string, unknown>): MacroRunFailure | undefined;
67
+ /**
68
+ * DSS Macros — the plugin "runnable" surface under
69
+ * `/public/api/projects/{pk}/runnables`.
70
+ *
71
+ * A macro run executes arbitrary plugin code on the DSS backend with
72
+ * side effects that DSS cannot predict; every method here that starts or
73
+ * stops a run is a write. Result bodies may be plain text or HTML rather
74
+ * than JSON, so {@link MacrosResource.result} returns the raw body.
75
+ */
76
+ export declare class MacrosResource extends BaseResource {
77
+ /**
78
+ * List the macros (runnables) available in a project. The official client
79
+ * lists at `/runnables/` (trailing slash): the slashless path 404s on live
80
+ * DSS 15, while get/run/state/result stay slashless.
81
+ */
82
+ list(projectKey?: string): Promise<MacroSummary[]>;
83
+ /** Get the definition of a macro: params, result type, owning plugin. */
84
+ definition(runnableType: string, opts?: {
85
+ projectKey?: string;
86
+ }): Promise<MacroDefinition>;
87
+ /**
88
+ * Start a macro run. Executes arbitrary plugin code: classify as
89
+ * destructive. With `wait: true` the server call blocks until completion
90
+ * and the resolved state is attached to the returned run handle.
91
+ */
92
+ run(runnableType: string, opts?: MacroRunOptions): Promise<MacroRunHandle | MacroRunWaitedResult>;
93
+ /** Request abort of a running macro run (204 response). */
94
+ abort(runnableType: string, runId: string, projectKey?: string): Promise<void>;
95
+ /** Get the poll state of a macro run (running flag, progress, errors). */
96
+ state(runnableType: string, runId: string, opts?: {
97
+ projectKey?: string;
98
+ /**
99
+ * TOTAL duration budget in ms for this state GET, measured from
100
+ * call start (bounds fetch, retries, backoff, and body read).
101
+ * Polling loops pass their remaining wait budget so a stalled
102
+ * state endpoint cannot outlive the wait deadline.
103
+ */
104
+ timeoutMs?: number;
105
+ }): Promise<MacroState>;
106
+ /**
107
+ * Download the result of a macro run. Macro results are documents of the
108
+ * macro's declared `resultType` (HTML, text, JSON, …) and are frequently
109
+ * NOT JSON: the raw body is returned verbatim. JSON bodies are returned
110
+ * as parsed values so programmatic callers can read fields directly.
111
+ */
112
+ result(runnableType: string, runId: string, opts?: MacroResultOptions): Promise<unknown>;
113
+ /**
114
+ * Run a macro and poll its state until it finishes or times out, using a
115
+ * bounded client-side loop on the state endpoint. Returns
116
+ * `{ success: false, timedOut: true }` on timeout rather than throwing.
117
+ */
118
+ runAndWait(runnableType: string, opts?: MacroWaitOptions): Promise<MacroWaitResult>;
119
+ private base;
120
+ }
121
+ export { isRecord as isMacroStateRecord, macroRunFailure, };
@@ -0,0 +1,231 @@
1
+ import { ClientValidationError, DataikuError, } from "../errors.js";
2
+ import { MacroDefinitionSchema, MacroRunStartSchema, MacroStateSchema, MacroSummaryArraySchema, MacroWaitResultSchema, } from "../schemas.js";
3
+ import { computeNextPollDelayMs, isRequestDeadlineError, } from "../utils/polling.js";
4
+ import { BaseResource, requireNonEmpty, } from "./base.js";
5
+ const DEFAULT_POLL_INTERVAL_MS = 2_000;
6
+ const DEFAULT_TIMEOUT_MS = 120_000;
7
+ /** Hard floor for the result download cap; keeps a caller typo from hanging the read. */
8
+ const MIN_RESULT_MAX_BYTES = 1;
9
+ const MAX_RESULT_MAX_BYTES = 2 * 1024 * 1024 * 1024;
10
+ /**
11
+ * Normalize a byte cap coming from user input (CLI `--max-bytes`) into the
12
+ * inclusive range [1, 2 GiB], rejecting non-integers before any DSS request.
13
+ */
14
+ export function macroResultMaxBytes(maxBytes) {
15
+ const raw = maxBytes ?? 8 * 1024 * 1024;
16
+ if (!Number.isInteger(raw) || raw < MIN_RESULT_MAX_BYTES) {
17
+ throw new ClientValidationError(`maxBytes must be a positive integer >= ${String(MIN_RESULT_MAX_BYTES)} (got ${String(raw)},)`);
18
+ }
19
+ return Math.min(raw, MAX_RESULT_MAX_BYTES);
20
+ }
21
+ function isRecord(value) {
22
+ return typeof value === "object" && value !== null && !Array.isArray(value);
23
+ }
24
+ /**
25
+ * Build the run POST body from params/adminParams. DSS macro runs accept
26
+ * `params` keyed by the macro's declared param names and `adminParams` keyed
27
+ * by its declared adminParams names; both are optional.
28
+ */
29
+ function macroRunBody(params, adminParams) {
30
+ const body = {};
31
+ if (params !== undefined)
32
+ body["params"] = params;
33
+ if (adminParams !== undefined)
34
+ body["adminParams"] = adminParams;
35
+ return body;
36
+ }
37
+ function macroRunFailure(state) {
38
+ if (state["resultError"] !== undefined && state["resultError"] !== null) {
39
+ return { source: "resultError", details: state["resultError"], };
40
+ }
41
+ if (state["storedError"] !== undefined && state["storedError"] !== null) {
42
+ return { source: "storedError", details: state["storedError"], };
43
+ }
44
+ return undefined;
45
+ }
46
+ /**
47
+ * DSS Macros — the plugin "runnable" surface under
48
+ * `/public/api/projects/{pk}/runnables`.
49
+ *
50
+ * A macro run executes arbitrary plugin code on the DSS backend with
51
+ * side effects that DSS cannot predict; every method here that starts or
52
+ * stops a run is a write. Result bodies may be plain text or HTML rather
53
+ * than JSON, so {@link MacrosResource.result} returns the raw body.
54
+ */
55
+ export class MacrosResource extends BaseResource {
56
+ /**
57
+ * List the macros (runnables) available in a project. The official client
58
+ * lists at `/runnables/` (trailing slash): the slashless path 404s on live
59
+ * DSS 15, while get/run/state/result stay slashless.
60
+ */
61
+ async list(projectKey) {
62
+ const raw = await this.client.get(`/public/api/projects/${this.enc(projectKey)}/runnables/`);
63
+ return this.client.safeParse(MacroSummaryArraySchema, raw, "macros.list");
64
+ }
65
+ /** Get the definition of a macro: params, result type, owning plugin. */
66
+ async definition(runnableType, opts) {
67
+ requireNonEmpty(runnableType, "runnableType");
68
+ const raw = await this.client.get(`${this.base(runnableType, opts?.projectKey)}`);
69
+ return this.client.safeParse(MacroDefinitionSchema, raw, "macros.definition");
70
+ }
71
+ /**
72
+ * Start a macro run. Executes arbitrary plugin code: classify as
73
+ * destructive. With `wait: true` the server call blocks until completion
74
+ * and the resolved state is attached to the returned run handle.
75
+ */
76
+ async run(runnableType, opts) {
77
+ requireNonEmpty(runnableType, "runnableType");
78
+ const wait = opts?.wait === true;
79
+ const query = wait ? "?wait=true" : "?wait=false";
80
+ const raw = await this.client.post(`${this.base(runnableType, opts?.projectKey)}${query}`, macroRunBody(opts?.params, opts?.adminParams));
81
+ const run = this.client.safeParse(MacroRunStartSchema, raw, "macros.run");
82
+ if (typeof run?.runId !== "string" || run.runId.trim().length === 0) {
83
+ throw new DataikuError(200, "Unexpected Response", "macros.run: DSS did not return a run identifier.");
84
+ }
85
+ if (!wait)
86
+ return run;
87
+ try {
88
+ const state = await this.state(runnableType, run.runId, {
89
+ projectKey: opts?.projectKey,
90
+ });
91
+ return { ...run, state, };
92
+ }
93
+ catch {
94
+ // The run started; a failed post-wait state probe must not lose the runId.
95
+ return run;
96
+ }
97
+ }
98
+ /** Request abort of a running macro run (204 response). */
99
+ async abort(runnableType, runId, projectKey) {
100
+ requireNonEmpty(runnableType, "runnableType");
101
+ requireNonEmpty(runId, "runId");
102
+ await this.client.post(`${this.base(runnableType, projectKey)}/abort/${encodeURIComponent(runId)}`, {});
103
+ }
104
+ /** Get the poll state of a macro run (running flag, progress, errors). */
105
+ async state(runnableType, runId, opts) {
106
+ requireNonEmpty(runnableType, "runnableType");
107
+ requireNonEmpty(runId, "runId");
108
+ const remainingMs = opts?.timeoutMs;
109
+ const raw = await this.client.get(`${this.base(runnableType, opts?.projectKey)}/state/${encodeURIComponent(runId)}`, ...(remainingMs !== undefined ? [{ timeoutMs: remainingMs, },] : []));
110
+ return this.client.safeParse(MacroStateSchema, raw, "macros.state");
111
+ }
112
+ /**
113
+ * Download the result of a macro run. Macro results are documents of the
114
+ * macro's declared `resultType` (HTML, text, JSON, …) and are frequently
115
+ * NOT JSON: the raw body is returned verbatim. JSON bodies are returned
116
+ * as parsed values so programmatic callers can read fields directly.
117
+ */
118
+ async result(runnableType, runId, opts) {
119
+ requireNonEmpty(runnableType, "runnableType");
120
+ requireNonEmpty(runId, "runId");
121
+ const maxBytes = macroResultMaxBytes(opts?.maxBytes);
122
+ const raw = await this.client.getTextLimited(`${this.base(runnableType, opts?.projectKey)}/result/${encodeURIComponent(runId)}`, maxBytes);
123
+ if (raw.truncated) {
124
+ throw new Error(`Macro result exceeded ${String(maxBytes)} bytes; retry with a larger maxBytes.`);
125
+ }
126
+ const text = raw.text;
127
+ const trimmed = text.trim();
128
+ if (trimmed.startsWith("{") && trimmed.endsWith("}")
129
+ || trimmed.startsWith("[") && trimmed.endsWith("]")) {
130
+ try {
131
+ return JSON.parse(text);
132
+ }
133
+ catch {
134
+ // Not JSON after all — fall through and return the text.
135
+ }
136
+ }
137
+ return text;
138
+ }
139
+ /**
140
+ * Run a macro and poll its state until it finishes or times out, using a
141
+ * bounded client-side loop on the state endpoint. Returns
142
+ * `{ success: false, timedOut: true }` on timeout rather than throwing.
143
+ */
144
+ async runAndWait(runnableType, opts) {
145
+ requireNonEmpty(runnableType, "runnableType");
146
+ // Validate the wait knobs BEFORE the run POST: an invalid budget must be
147
+ // a caller error, not a mutation whose wait behavior is undefined.
148
+ if (opts?.pollIntervalMs !== undefined) {
149
+ if (!Number.isFinite(opts.pollIntervalMs) || opts.pollIntervalMs < 1) {
150
+ throw new ClientValidationError("pollIntervalMs must be a finite number >= 1.");
151
+ }
152
+ }
153
+ if (opts?.timeoutMs !== undefined) {
154
+ if (!Number.isFinite(opts.timeoutMs) || opts.timeoutMs < 0) {
155
+ throw new ClientValidationError("timeoutMs must be a finite number >= 0.");
156
+ }
157
+ }
158
+ const baseIntervalMs = Math.max(1, opts?.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS);
159
+ const adaptivePolling = opts?.pollIntervalMs === undefined;
160
+ // One state observation always happens regardless of budget (poll
161
+ // precedes deadline check), but never round a budget up to a full poll.
162
+ const timeoutMs = Math.max(0, opts?.timeoutMs ?? DEFAULT_TIMEOUT_MS);
163
+ const startedAt = Date.now();
164
+ const run = await this.run(runnableType, {
165
+ wait: false,
166
+ projectKey: opts?.projectKey,
167
+ params: opts?.params,
168
+ adminParams: opts?.adminParams,
169
+ });
170
+ const runId = run.runId;
171
+ let pollCount = 0;
172
+ let lastState;
173
+ const timedOutResult = (elapsedMs, state) => this.client.safeParse(MacroWaitResultSchema, {
174
+ runnableType,
175
+ runId,
176
+ running: true,
177
+ success: false,
178
+ timedOut: true,
179
+ elapsedMs,
180
+ pollCount,
181
+ ...(state ? { state, } : {}),
182
+ }, "macros.runAndWait");
183
+ while (true) {
184
+ pollCount += 1;
185
+ try {
186
+ lastState = await this.state(runnableType, runId, {
187
+ projectKey: opts?.projectKey,
188
+ timeoutMs: Math.max(1, timeoutMs - (Date.now() - startedAt)),
189
+ });
190
+ }
191
+ catch (error) {
192
+ if (!isRequestDeadlineError(error, startedAt + timeoutMs))
193
+ throw error;
194
+ return timedOutResult(Date.now() - startedAt, lastState);
195
+ }
196
+ const running = lastState.running === true;
197
+ if (running !== true) {
198
+ return this.client.safeParse(MacroWaitResultSchema, {
199
+ runnableType,
200
+ runId,
201
+ running: false,
202
+ success: macroRunFailure(lastState) === undefined
203
+ && lastState.exists !== false,
204
+ elapsedMs: Date.now() - startedAt,
205
+ pollCount,
206
+ ...(macroRunFailure(lastState)
207
+ ? { failure: macroRunFailure(lastState), }
208
+ : {}),
209
+ state: lastState,
210
+ }, "macros.runAndWait");
211
+ }
212
+ const elapsedMs = Date.now() - startedAt;
213
+ if (elapsedMs >= timeoutMs) {
214
+ return timedOutResult(elapsedMs, lastState);
215
+ }
216
+ const nextDelayMs = computeNextPollDelayMs({
217
+ pollCount,
218
+ baseIntervalMs,
219
+ adaptiveEnabled: adaptivePolling,
220
+ });
221
+ const { promise, resolve, } = Promise.withResolvers();
222
+ setTimeout(resolve, Math.min(nextDelayMs, Math.max(1, timeoutMs - (Date.now() - startedAt))));
223
+ await promise;
224
+ }
225
+ }
226
+ base(runnableType, projectKey) {
227
+ return `/public/api/projects/${this.enc(projectKey)}/runnables/${encodeURIComponent(runnableType)}`;
228
+ }
229
+ }
230
+ // Re-exported for CLI/plan use: failure extraction from a poll state record.
231
+ export { isRecord as isMacroStateRecord, macroRunFailure, };
@@ -39,6 +39,19 @@ export interface MlTaskTrainOptions {
39
39
  sessionDescription?: string;
40
40
  runQueue?: boolean;
41
41
  wait?: boolean;
42
+ /**
43
+ * Deadline in milliseconds for the whole `wait` phase after DSS has
44
+ * accepted the training request. Validated before any request is sent:
45
+ * an invalid deadline must not start training work on the server.
46
+ * `timeoutMs: 0` disables waiting (single status check). Requires `wait`.
47
+ */
48
+ timeoutMs?: number;
49
+ /**
50
+ * Fixed poll interval in milliseconds for the training status loop.
51
+ * When omitted the loop backs off adaptively (doubles every 3 polls, capped).
52
+ * Requires `wait`.
53
+ */
54
+ pollIntervalMs?: number;
42
55
  projectKey?: string;
43
56
  }
44
57
  export interface MlTaskTrainingSession extends Record<string, unknown> {
@@ -48,7 +61,17 @@ export interface MlTaskTrainCompletedResult extends Record<string, unknown> {
48
61
  sessionId: string;
49
62
  trainedModelIds: string[];
50
63
  }
51
- export type MlTaskTrainResult = MlTaskTrainingSession | MlTaskTrainCompletedResult;
64
+ /** Result returned when the training wait budget was exhausted. */
65
+ export interface MlTaskTrainTimedOutResult extends Record<string, unknown> {
66
+ sessionId: string;
67
+ trainedModelIds: string[];
68
+ timedOut: true;
69
+ success: false;
70
+ state: "RUNNING";
71
+ elapsedMs: number;
72
+ pollCount: number;
73
+ }
74
+ export type MlTaskTrainResult = MlTaskTrainingSession | MlTaskTrainCompletedResult | MlTaskTrainTimedOutResult;
52
75
  export interface MlTrainedModelDetails extends Record<string, unknown> {
53
76
  id?: string;
54
77
  algorithm?: string;
@@ -77,7 +100,19 @@ export declare class MlTasksResource extends BaseResource {
77
100
  getSettings(analysisId: string, mlTaskId: string, projectKey?: string): Promise<MlTaskSettings>;
78
101
  /** Save the full settings object of an ML task. */
79
102
  saveSettings(analysisId: string, mlTaskId: string, settings: MlTaskSettings, projectKey?: string): Promise<MlTaskActionResult | undefined>;
80
- /** Start training, optionally waiting for the task to finish. */
103
+ /**
104
+ * Start training, optionally waiting for the task to finish.
105
+ *
106
+ * `timeoutMs` is validated BEFORE the training POST: an invalid deadline
107
+ * never reaches the server, so bad input cannot start training work. Once
108
+ * DSS has accepted the training request, the deadline scopes only the
109
+ * wait phase — each status poll is issued with the remaining budget as a
110
+ * total request deadline, so a hung status request cannot outlast it.
111
+ *
112
+ * When the budget is exhausted the returned result keeps the documented
113
+ * success shape plus `timedOut: true` and `success: false` instead of
114
+ * throwing, so a caller can distinguish "still training" from "finished".
115
+ */
81
116
  train(opts: MlTaskTrainOptions): Promise<MlTaskTrainResult>;
82
117
  /** List identifiers for every trained model currently present on the task. */
83
118
  listTrainedModels(analysisId: string, mlTaskId: string, projectKey?: string): Promise<string[]>;
@@ -1,12 +1,8 @@
1
- import { UsageError, } from "../cli/usage.js";
2
- import { BaseResource, } from "./base.js";
1
+ import { ClientValidationError, } from "../errors.js";
2
+ import { computeNextPollDelayMs, isRequestDeadlineError, } from "../utils/polling.js";
3
+ import { BaseResource, requireNonEmpty, } from "./base.js";
3
4
  const TRAIN_POLL_INTERVAL_MS = 5_000;
4
- function requireNonEmpty(value, name) {
5
- if (typeof value !== "string" || value.trim().length === 0) {
6
- throw new UsageError(`${name} must be a non-empty string.`, "validation_failed");
7
- }
8
- return value;
9
- }
5
+ const TRAIN_DEFAULT_TIMEOUT_MS = 120_000;
10
6
  function delay(ms) {
11
7
  const { promise, resolve, } = Promise.withResolvers();
12
8
  setTimeout(resolve, ms);
@@ -26,11 +22,11 @@ export class MlTasksResource extends BaseResource {
26
22
  /** Create a prediction or clustering task in an existing visual analysis. */
27
23
  async create(opts) {
28
24
  if (opts.taskType !== "PREDICTION" && opts.taskType !== "CLUSTERING") {
29
- throw new UsageError("taskType must be PREDICTION or CLUSTERING.", "invalid_enum");
25
+ throw new ClientValidationError("taskType must be PREDICTION or CLUSTERING.", "invalid_enum");
30
26
  }
31
27
  if (opts.taskType === "PREDICTION"
32
28
  && (typeof opts.targetVariable !== "string" || opts.targetVariable.trim().length === 0)) {
33
- throw new UsageError("targetVariable is required for PREDICTION ML tasks.", "missing_required_arg");
29
+ throw new ClientValidationError("targetVariable is required for PREDICTION ML tasks.", "missing_required_arg");
34
30
  }
35
31
  const analysisId = encodeURIComponent(requireNonEmpty(opts.analysisId, "analysisId"));
36
32
  const backendType = opts.backendType ?? "PY_MEMORY";
@@ -65,17 +61,49 @@ export class MlTasksResource extends BaseResource {
65
61
  /** Save the full settings object of an ML task. */
66
62
  async saveSettings(analysisId, mlTaskId, settings, projectKey) {
67
63
  if (settings === null || typeof settings !== "object" || Array.isArray(settings)) {
68
- throw new UsageError("settings must be an object.", "validation_failed");
64
+ throw new ClientValidationError("settings must be an object.", "validation_failed");
69
65
  }
70
66
  return this.client.post(`${this.taskPath(analysisId, mlTaskId, projectKey)}/settings`, settings);
71
67
  }
72
- /** Start training, optionally waiting for the task to finish. */
68
+ /**
69
+ * Start training, optionally waiting for the task to finish.
70
+ *
71
+ * `timeoutMs` is validated BEFORE the training POST: an invalid deadline
72
+ * never reaches the server, so bad input cannot start training work. Once
73
+ * DSS has accepted the training request, the deadline scopes only the
74
+ * wait phase — each status poll is issued with the remaining budget as a
75
+ * total request deadline, so a hung status request cannot outlast it.
76
+ *
77
+ * When the budget is exhausted the returned result keeps the documented
78
+ * success shape plus `timedOut: true` and `success: false` instead of
79
+ * throwing, so a caller can distinguish "still training" from "finished".
80
+ */
73
81
  async train(opts) {
74
82
  if (opts.sessionName !== undefined)
75
83
  requireNonEmpty(opts.sessionName, "sessionName");
76
84
  if (opts.sessionDescription !== undefined) {
77
85
  requireNonEmpty(opts.sessionDescription, "sessionDescription");
78
86
  }
87
+ // Deadline validation happens before any network request: a malformed
88
+ // budget must not start training work on the server.
89
+ const explicitIntervalMs = opts.pollIntervalMs;
90
+ if (explicitIntervalMs !== undefined && opts.wait !== true) {
91
+ throw new ClientValidationError("pollIntervalMs requires wait: true.", "validation_failed");
92
+ }
93
+ if (explicitIntervalMs !== undefined
94
+ && (!Number.isFinite(explicitIntervalMs) || explicitIntervalMs <= 0)) {
95
+ throw new ClientValidationError("pollIntervalMs must be a finite positive number.", "validation_failed");
96
+ }
97
+ let timeoutMs;
98
+ if (opts.timeoutMs !== undefined) {
99
+ if (opts.wait !== true) {
100
+ throw new ClientValidationError("timeoutMs requires wait: true.", "validation_failed");
101
+ }
102
+ if (!Number.isFinite(opts.timeoutMs) || opts.timeoutMs < 0) {
103
+ throw new ClientValidationError("timeoutMs must be a finite non-negative number.", "validation_failed");
104
+ }
105
+ timeoutMs = opts.timeoutMs;
106
+ }
79
107
  const taskPath = this.taskPath(opts.analysisId, opts.mlTaskId, opts.projectKey);
80
108
  const session = await this.client.post(`${taskPath}/train`, {
81
109
  sessionName: opts.sessionName,
@@ -84,15 +112,76 @@ export class MlTasksResource extends BaseResource {
84
112
  });
85
113
  if (opts.wait !== true)
86
114
  return session;
87
- let status = await this.status(opts.analysisId, opts.mlTaskId, opts.projectKey);
88
- while (status.training !== false) {
89
- await delay(TRAIN_POLL_INTERVAL_MS);
90
- status = await this.status(opts.analysisId, opts.mlTaskId, opts.projectKey);
115
+ const deadlineMs = timeoutMs ?? TRAIN_DEFAULT_TIMEOUT_MS;
116
+ const baseIntervalMs = Math.max(1, explicitIntervalMs ?? TRAIN_POLL_INTERVAL_MS);
117
+ const adaptiveEnabled = explicitIntervalMs === undefined;
118
+ const startedAt = Date.now();
119
+ let pollCount = 0;
120
+ let lastStatus;
121
+ while (true) {
122
+ const elapsedBeforeMs = Date.now() - startedAt;
123
+ // The first observation always happens, even when the budget is
124
+ // already spent. A later poll is never started after the deadline:
125
+ // the loop reports the structured timeout from the last observed
126
+ // status instead of issuing a request the budget cannot cover.
127
+ if (lastStatus !== undefined && elapsedBeforeMs >= deadlineMs) {
128
+ return {
129
+ sessionId: session.sessionId,
130
+ trainedModelIds: modelIdsFromStatus(lastStatus),
131
+ timedOut: true,
132
+ success: false,
133
+ state: "RUNNING",
134
+ elapsedMs: elapsedBeforeMs,
135
+ pollCount,
136
+ };
137
+ }
138
+ pollCount += 1;
139
+ // Requests issued while budget remains are bounded by the remaining
140
+ // time; a spent budget still issues exactly one transport attempt
141
+ // (no retries, client requestTimeoutMs cap) so the first observation
142
+ // reaches the server instead of failing before any attempt.
143
+ const remainingMs = deadlineMs - elapsedBeforeMs;
144
+ let status;
145
+ try {
146
+ status = await this.client.get(`${this.taskPath(opts.analysisId, opts.mlTaskId, opts.projectKey)}/status`, remainingMs > 0
147
+ ? { timeoutMs: remainingMs, }
148
+ : { noRetry: true, });
149
+ }
150
+ catch (error) {
151
+ // The poll budget ran out: report the structured timeout instead
152
+ // of letting the transport deadline error escape the loop.
153
+ if (!isRequestDeadlineError(error, startedAt + deadlineMs))
154
+ throw error;
155
+ return {
156
+ sessionId: session.sessionId,
157
+ trainedModelIds: [],
158
+ timedOut: true,
159
+ success: false,
160
+ state: "RUNNING",
161
+ elapsedMs: Date.now() - startedAt,
162
+ pollCount,
163
+ };
164
+ }
165
+ lastStatus = status;
166
+ const elapsedMs = Date.now() - startedAt;
167
+ if (status.training !== false) {
168
+ // Deadline reached: the guard at the top of the loop reports the
169
+ // timeout from this status without issuing another request.
170
+ if (elapsedMs >= deadlineMs)
171
+ continue;
172
+ const nextDelayMs = computeNextPollDelayMs({
173
+ pollCount,
174
+ baseIntervalMs,
175
+ adaptiveEnabled,
176
+ });
177
+ await delay(Math.min(nextDelayMs, deadlineMs - elapsedMs));
178
+ continue;
179
+ }
180
+ return {
181
+ sessionId: session.sessionId,
182
+ trainedModelIds: modelIdsFromStatus(status),
183
+ };
91
184
  }
92
- return {
93
- sessionId: session.sessionId,
94
- trainedModelIds: modelIdsFromStatus(status),
95
- };
96
185
  }
97
186
  /** List identifiers for every trained model currently present on the task. */
98
187
  async listTrainedModels(analysisId, mlTaskId, projectKey) {
@@ -1,11 +1,4 @@
1
- import { UsageError, } from "../cli/usage.js";
2
- import { BaseResource, } from "./base.js";
3
- function requireNonEmpty(value, name) {
4
- if (typeof value !== "string" || value.trim().length === 0) {
5
- throw new UsageError(`${name} must be a non-empty string.`, "validation_failed");
6
- }
7
- return value;
8
- }
1
+ import { BaseResource, requireNonEmpty, } from "./base.js";
9
2
  export class ModelEvaluationStoresResource extends BaseResource {
10
3
  /** List model evaluation stores in a project. */
11
4
  async list(projectKey) {
@@ -7,6 +7,15 @@ export interface NotebookSaveResult {
7
7
  /** SHA-256 (hex) of the persisted content after a confirming re-read. */
8
8
  hash: string;
9
9
  }
10
+ /**
11
+ * SQL-notebook save result. `id` is the persisted notebook identifier: on a
12
+ * create DSS allocates it server-side (a request-supplied `id` is rejected),
13
+ * so every later read, update, and delete must use this id — the requested
14
+ * handle only becomes the notebook's display name.
15
+ */
16
+ export interface SqlNotebookSaveResult extends NotebookSaveResult {
17
+ id: string;
18
+ }
10
19
  export interface SaveNotebookOptions {
11
20
  /** Reject the save when the stored content hash no longer matches. */
12
21
  expectHash?: string;
@@ -84,16 +93,43 @@ export declare class NotebooksResource extends BaseResource {
84
93
  listSql(projectKey?: string): Promise<SqlNotebookSummary[]>;
85
94
  /** Get the full content of a SQL notebook. */
86
95
  getSql(id: string, projectKey?: string): Promise<SqlNotebookContent>;
87
- /** Create a SQL notebook. */
88
- createSql(id: string, content: SqlNotebookContent, projectKey?: string): Promise<void>;
89
- /** Save (overwrite) a SQL notebook's content. */
96
+ /**
97
+ * Create a SQL notebook.
98
+ *
99
+ * DSS allocates the notebook id server-side and rejects a request-supplied
100
+ * `id` (`Field 'id' is specified, it must not be specified`); `name` is
101
+ * the display handle (the requested label) when provided. The returned id
102
+ * comes from the creation receipt and is the only handle later reads,
103
+ * updates, and deletes may use.
104
+ */
105
+ createSql(content: SqlNotebookContent, projectKey?: string, opts?: {
106
+ name?: string;
107
+ }): Promise<{
108
+ id: string;
109
+ }>;
110
+ /**
111
+ * Save (overwrite) a SQL notebook's content.
112
+ *
113
+ * Identity is positional: the URL id and the resolved project key are
114
+ * injected into the body, overriding any `id`/`projectKey` the content may
115
+ * carry, so a stale or hand-built body can never retarget the write. PUT
116
+ * is a full replacement — fields absent from the content (e.g. `name`)
117
+ * are cleared server-side.
118
+ */
90
119
  saveSql(id: string, content: SqlNotebookContent, projectKey?: string): Promise<void>;
91
120
  /**
92
- * Save a SQL notebook, creating it when missing. Same contract as
93
- * `saveOrCreateJupyter`: `created` reflects the fresh read, and `hash` is
94
- * the hash of the persisted content from a confirming re-read.
121
+ * Save a SQL notebook, creating it when missing.
122
+ *
123
+ * Update path: the fresh read finds the notebook and a PUT overwrites it.
124
+ * Create path: DSS allocates the persisted id (the requested handle becomes
125
+ * the display name; there is no name-based re-lookup, so a foreign
126
+ * notebook can never be mistaken for the target), the id is returned, and
127
+ * every later read/update/delete must use that returned id. `created`
128
+ * reflects the fresh read; `hash` is the hash of the persisted content
129
+ * from a confirming re-read. The armed `--expect-hash` guard also refuses
130
+ * an absent target.
95
131
  */
96
- saveOrCreateSql(id: string, content: SqlNotebookContent, projectKey?: string, opts?: SaveNotebookOptions): Promise<NotebookSaveResult>;
132
+ saveOrCreateSql(id: string, content: SqlNotebookContent, projectKey?: string, opts?: SaveNotebookOptions): Promise<SqlNotebookSaveResult>;
97
133
  /** Delete a SQL notebook. */
98
134
  deleteSql(id: string, projectKey?: string): Promise<void>;
99
135
  /** Get execution history for a SQL notebook (keyed by cell ID). */