@toloka-platform/sdk 0.1.0-preview.5 → 0.1.0-preview.6
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/dist/cli/toloka.js +121 -120
- package/dist/cli/toloka.js.jsc +0 -0
- package/dist/cli/toloka.js.jsc.sha256 +1 -1
- package/dist/cli/toloka.js.sha256 +1 -1
- package/docs/cli.md +26 -1
- package/docs/datasets.md +8 -9
- package/docs/limitations.md +5 -8
- package/docs/troubleshooting.md +0 -11
- package/openapi/gen/api.ts +779 -895
- package/openapi/gen/models/baseModel.ts +9 -0
- package/openapi/gen/models/baseModelId.ts +2 -0
- package/openapi/gen/models/baseModelIdProperty.ts +2 -0
- package/openapi/gen/models/createProjectModelBody.ts +8 -0
- package/openapi/gen/models/createProjectModelBodyDescription.ts +2 -0
- package/openapi/gen/models/generationNodeSettingsAudienceAndPricingOneOfOneone.ts +1 -0
- package/openapi/gen/models/index.ts +16 -0
- package/openapi/gen/models/listBaseModels200.ts +5 -0
- package/openapi/gen/models/listModelVersions200.ts +7 -0
- package/openapi/gen/models/listModelVersionsParams.ts +5 -0
- package/openapi/gen/models/listProjectModels200.ts +7 -0
- package/openapi/gen/models/listProjectModelsParams.ts +5 -0
- package/openapi/gen/models/model.ts +16 -0
- package/openapi/gen/models/modelArchivedAt.ts +2 -0
- package/openapi/gen/models/modelDescription.ts +2 -0
- package/openapi/gen/models/modelId.ts +2 -0
- package/openapi/gen/models/modelVersion.ts +9 -0
- package/openapi/gen/models/modelVersionId.ts +2 -0
- package/openapi/gen/models/pipelineGraphSnapshotGraphNodesItemOneOfThreeoneSettingsAudienceAndPricingOneOfOneone.ts +1 -0
- package/openapi/gen/models/validatePipelineGraphBodyGraphNodesItemOneOfThreeoneSettingsAudienceAndPricingOneOfOneone.ts +1 -0
- package/package.json +1 -1
- package/skills/catalog.json +4 -4
- package/skills/toloka-build/SKILL.md +1 -1
- package/skills/toloka-build/references/workflow-and-cli.md +12 -1
- package/skills/toloka-report-issue/references/report-template.md +1 -1
- package/src/cli/data.ts +148 -25
- package/src/cli/models.ts +188 -0
- package/src/cli/program.ts +99 -1
- package/src/client.ts +109 -35
- package/src/errors.ts +25 -10
- package/src/transport.ts +16 -13
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import type { CreateProjectModelBodyDescription } from './createProjectModelBodyDescription';
|
|
2
|
+
import type { BaseModelId } from './baseModelId';
|
|
3
|
+
|
|
4
|
+
export type CreateProjectModelBody = {
|
|
5
|
+
name: string;
|
|
6
|
+
description?: CreateProjectModelBodyDescription;
|
|
7
|
+
baseModelId: BaseModelId;
|
|
8
|
+
};
|
|
@@ -6,4 +6,5 @@ export type GenerationNodeSettingsAudienceAndPricingOneOfOneone = {
|
|
|
6
6
|
generationModelId?: GenerationNodeSettingsAudienceAndPricingOneOfOneoneGenerationModelId;
|
|
7
7
|
reasoningEffort?: GenerationNodeSettingsAudienceAndPricingOneOfOneoneReasoningEffort;
|
|
8
8
|
llmInstruction?: string;
|
|
9
|
+
maxCostPerAttempt: number;
|
|
9
10
|
};
|
|
@@ -19,6 +19,9 @@ export * from './attachPipelineDatasetBodyItemFilterFieldValuesItemSourcesItem';
|
|
|
19
19
|
export * from './attachPipelineDatasetBodyItemFilterFieldValuesItemSourcesItemOneOf';
|
|
20
20
|
export * from './attachPipelineDatasetBodyItemFilterFieldValuesItemSourcesItemOneOfTwo';
|
|
21
21
|
export * from './attachPipelineDatasetBodyItemFilterFieldValuesItemValue';
|
|
22
|
+
export * from './baseModel';
|
|
23
|
+
export * from './baseModelId';
|
|
24
|
+
export * from './baseModelIdProperty';
|
|
22
25
|
export * from './checkJob';
|
|
23
26
|
export * from './checkJobError';
|
|
24
27
|
export * from './checkJobResult';
|
|
@@ -96,6 +99,8 @@ export * from './createProjectBody';
|
|
|
96
99
|
export * from './createProjectBodyDeliveryOrderId';
|
|
97
100
|
export * from './createProjectFileUpload200';
|
|
98
101
|
export * from './createProjectFileUploadBody';
|
|
102
|
+
export * from './createProjectModelBody';
|
|
103
|
+
export * from './createProjectModelBodyDescription';
|
|
99
104
|
export * from './createdDatasetFields';
|
|
100
105
|
export * from './currentUser';
|
|
101
106
|
export * from './currentUserEmail';
|
|
@@ -249,10 +254,13 @@ export * from './jsonSchema';
|
|
|
249
254
|
export * from './jsonSchemaAdditionalProperties';
|
|
250
255
|
export * from './jsonSchemaType';
|
|
251
256
|
export * from './jsonSchemaTypeAnyOfItem';
|
|
257
|
+
export * from './listBaseModels200';
|
|
252
258
|
export * from './listDatasetFields200';
|
|
253
259
|
export * from './listDatasetFieldsParams';
|
|
254
260
|
export * from './listDatasetItems200';
|
|
255
261
|
export * from './listDatasetItemsParams';
|
|
262
|
+
export * from './listModelVersions200';
|
|
263
|
+
export * from './listModelVersionsParams';
|
|
256
264
|
export * from './listPipelineChecks200';
|
|
257
265
|
export * from './listPipelineRunItems200';
|
|
258
266
|
export * from './listPipelineRunItemsParams';
|
|
@@ -261,6 +269,8 @@ export * from './listPipelineRunsParams';
|
|
|
261
269
|
export * from './listProjectDatasets200';
|
|
262
270
|
export * from './listProjectDatasetsParams';
|
|
263
271
|
export * from './listProjectFiles200';
|
|
272
|
+
export * from './listProjectModels200';
|
|
273
|
+
export * from './listProjectModelsParams';
|
|
264
274
|
export * from './listProjectPipelines200';
|
|
265
275
|
export * from './listProjectPipelinesParams';
|
|
266
276
|
export * from './listProjectSecrets200';
|
|
@@ -277,6 +287,12 @@ export * from './matchNodeSettingsConditionsItemPredicatesItemOperator';
|
|
|
277
287
|
export * from './matchNodeSettingsConditionsItemPredicatesItemValue';
|
|
278
288
|
export * from './matchNodeSettingsFieldProxies';
|
|
279
289
|
export * from './matchNodeSettingsFieldProxiesInputItem';
|
|
290
|
+
export * from './model';
|
|
291
|
+
export * from './modelArchivedAt';
|
|
292
|
+
export * from './modelDescription';
|
|
293
|
+
export * from './modelId';
|
|
294
|
+
export * from './modelVersion';
|
|
295
|
+
export * from './modelVersionId';
|
|
280
296
|
export * from './pagination';
|
|
281
297
|
export * from './patchPipelineNodeBody';
|
|
282
298
|
export * from './patchPipelineNodeBodySettings';
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import type { ModelId } from './modelId';
|
|
2
|
+
import type { ProjectId } from './projectId';
|
|
3
|
+
import type { ModelDescription } from './modelDescription';
|
|
4
|
+
import type { BaseModelId } from './baseModelId';
|
|
5
|
+
import type { ModelArchivedAt } from './modelArchivedAt';
|
|
6
|
+
|
|
7
|
+
export interface Model {
|
|
8
|
+
id: ModelId;
|
|
9
|
+
projectId: ProjectId;
|
|
10
|
+
name: string;
|
|
11
|
+
description: ModelDescription;
|
|
12
|
+
baseModelId: BaseModelId;
|
|
13
|
+
archivedAt: ModelArchivedAt;
|
|
14
|
+
createdAt: string;
|
|
15
|
+
updatedAt: string;
|
|
16
|
+
}
|
|
@@ -6,4 +6,5 @@ export type PipelineGraphSnapshotGraphNodesItemOneOfThreeoneSettingsAudienceAndP
|
|
|
6
6
|
generationModelId?: PipelineGraphSnapshotGraphNodesItemOneOfThreeoneSettingsAudienceAndPricingOneOfOneoneGenerationModelId;
|
|
7
7
|
reasoningEffort?: PipelineGraphSnapshotGraphNodesItemOneOfThreeoneSettingsAudienceAndPricingOneOfOneoneReasoningEffort;
|
|
8
8
|
llmInstruction?: string;
|
|
9
|
+
maxCostPerAttempt: number;
|
|
9
10
|
};
|
|
@@ -6,4 +6,5 @@ export type ValidatePipelineGraphBodyGraphNodesItemOneOfThreeoneSettingsAudience
|
|
|
6
6
|
generationModelId?: ValidatePipelineGraphBodyGraphNodesItemOneOfThreeoneSettingsAudienceAndPricingOneOfOneoneGenerationModelId;
|
|
7
7
|
reasoningEffort?: ValidatePipelineGraphBodyGraphNodesItemOneOfThreeoneSettingsAudienceAndPricingOneOfOneoneReasoningEffort;
|
|
8
8
|
llmInstruction?: string;
|
|
9
|
+
maxCostPerAttempt: number;
|
|
9
10
|
};
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@toloka-platform/sdk",
|
|
3
|
-
"version": "0.1.0-preview.
|
|
3
|
+
"version": "0.1.0-preview.6",
|
|
4
4
|
"private": false,
|
|
5
5
|
"description": "Preview. Build, inspect, and operate Toloka pipeline projects as TypeScript source. The public API may change.",
|
|
6
6
|
"license": "Apache-2.0",
|
package/skills/catalog.json
CHANGED
|
@@ -5,8 +5,8 @@
|
|
|
5
5
|
"name": "Build Toloka pipelines as code",
|
|
6
6
|
"description": "Build, inspect, and change Toloka platform pipeline projects as TypeScript source with @toloka-platform/sdk and toloka. Use when the user wants to create, adopt, inspect, plan, or apply a pipeline project as code.",
|
|
7
7
|
"tags": ["pipelines", "authoring", "sdk"],
|
|
8
|
-
"version": "0.1.0-preview.
|
|
9
|
-
"digest": "
|
|
8
|
+
"version": "0.1.0-preview.6",
|
|
9
|
+
"digest": "d45d1124d27e19ee43c260ddf0c259ccdc343c5d641c2aeb538d6fedaeb99183",
|
|
10
10
|
"files": ["SKILL.md", "references/node-semantics.md", "references/workflow-and-cli.md"]
|
|
11
11
|
},
|
|
12
12
|
{
|
|
@@ -14,8 +14,8 @@
|
|
|
14
14
|
"name": "Report a Toloka problem",
|
|
15
15
|
"description": "Report a problem with the Toloka platform, CLI, or SDK as a redacted Markdown issue report sent through the toloka CLI only after explicit approval. Use when the user hits unexpected behavior, an error, or a bug and wants Toloka engineers to investigate.",
|
|
16
16
|
"tags": ["support", "issue", "bug", "report"],
|
|
17
|
-
"version": "0.1.0-preview.
|
|
18
|
-
"digest": "
|
|
17
|
+
"version": "0.1.0-preview.6",
|
|
18
|
+
"digest": "b71d06333786826edd51b909dfe8a20633870f235d03df4aae83d20c26e99205",
|
|
19
19
|
"files": ["SKILL.md", "references/report-template.md"]
|
|
20
20
|
}
|
|
21
21
|
]
|
|
@@ -66,7 +66,7 @@ A successful `toloka validate` is necessary, not proof the pipeline can be launc
|
|
|
66
66
|
|
|
67
67
|
## Keep data and credentials out of source
|
|
68
68
|
|
|
69
|
-
- Never put dataset items in pipeline source or `toloka.json`. After apply has created or resolved the dataset, upload
|
|
69
|
+
- Never put dataset items in pipeline source or `toloka.json`. After apply has created or resolved the dataset, upload `.json`, `.jsonl`, or `.csv` rows with `toloka dataset upload <dataset> <file>`. If a later upload fails as schema-incompatible, run `toloka dataset fields <dataset>` and copy the printed `field()` declarations into source.
|
|
70
70
|
- Put secret references in node settings, but never secret values. Set values through stdin with `toloka secret set <name>`, or use `toloka secret set <name> --from-env <variable>`.
|
|
71
71
|
|
|
72
72
|
## Credentials
|
|
@@ -109,6 +109,13 @@ toloka pipeline runs [name] [--limit <n>]
|
|
|
109
109
|
toloka pipeline cost [name] [--item-limit <n>]
|
|
110
110
|
toloka pipeline results <runId> [--limit <n>] [--failed]
|
|
111
111
|
toloka pipeline cancel <runId>
|
|
112
|
+
toloka base-model list [--json]
|
|
113
|
+
toloka base-model get <id> [--json]
|
|
114
|
+
toloka model list [--json]
|
|
115
|
+
toloka model get <name-or-id> [--json]
|
|
116
|
+
toloka model versions <name-or-id> [--json]
|
|
117
|
+
toloka model create <name> --base <base-model-id> [--description <text>] [--json]
|
|
118
|
+
toloka model archive <name-or-id> [--json]
|
|
112
119
|
toloka skill list [--json]
|
|
113
120
|
toloka skill search <query> [--json]
|
|
114
121
|
toloka skill install <skill-id> [--force]
|
|
@@ -121,6 +128,10 @@ Select the platform project with `toloka project add` or `toloka project create`
|
|
|
121
128
|
|
|
122
129
|
`toloka pipeline cost [name] [--item-limit <n>]` forecasts what a run of a live pipeline would bill. It does not start a run and does not change the graph. Name the live pipeline or id; a TTY may pick among several. The typed client equivalent is `client.pipelines.estimateCost({ pipelineId, itemLimit? })`.
|
|
123
130
|
|
|
131
|
+
## Manage model checkpoints
|
|
132
|
+
|
|
133
|
+
The global Base Model Catalog is read-only and uses `toloka base-model`. `base-model list` prints aligned ID, name, provider, and upstream-model columns by default. `toloka model` manages project-owned lineages; creation requires one catalog `baseModelId` and creates metadata without a Model Version. Every Base Model and Model command accepts `--json` for machine-readable output. Model Version upload, validation, and download commands are deferred until the separate upload-processing contract is implemented.
|
|
134
|
+
|
|
124
135
|
## Adopt an existing live project
|
|
125
136
|
|
|
126
137
|
1. Run `toloka init` in the intended source root if the agent entry points are not installed yet.
|
|
@@ -186,7 +197,7 @@ Declare the fields in source, then upload rows:
|
|
|
186
197
|
toloka dataset upload <dataset> <file>
|
|
187
198
|
```
|
|
188
199
|
|
|
189
|
-
The input
|
|
200
|
+
The CLI infers the input format from its extension and uploads the original bytes without conversion: `.json` is an array of row objects, `.jsonl` has one object per non-empty line, and `.csv` uses its header row as field names with every value ingested as a string. JSONL is validated locally before upload. If transfer or finalization fails, the CLI cancels the started upload before returning the error, so another upload can start immediately and may reuse the same filename. The dataset does not have to exist yet: when a pipeline in source binds it, upload creates it and declares that pipeline's columns, so rows land in real columns rather than in the raw `uploaded` blob. Upload refuses when no pipeline in source declares a name because it would have no column names to map onto. An id-shaped argument is never created; upload fails when that dataset id does not exist in the project. Upload appends items; applying source never deletes them.
|
|
190
201
|
|
|
191
202
|
If a later upload fails with a schema-incompatibility error, inspect the inferred schema from the first successful upload:
|
|
192
203
|
|
|
@@ -26,7 +26,7 @@ The CLI exits 1 with `Potato API request failed with status 500`. The same comma
|
|
|
26
26
|
|
|
27
27
|
## Environment
|
|
28
28
|
|
|
29
|
-
- toloka CLI 0.1.0-preview.
|
|
29
|
+
- toloka CLI 0.1.0-preview.6 (`toloka --version`), Bun 1.4.0, macOS 15
|
|
30
30
|
- Platform: https://platform.toloka.ai
|
|
31
31
|
|
|
32
32
|
## Redactions
|
package/src/cli/data.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { assert } from "es-toolkit";
|
|
2
2
|
import { mkdir, readFile, writeFile } from "node:fs/promises";
|
|
3
3
|
import { basename, dirname } from "node:path";
|
|
4
|
-
import type { Dataset, InferredField, PotatoClient } from "../client";
|
|
4
|
+
import type { Dataset, InferredField, PotatoClient, UploadId } from "../client";
|
|
5
5
|
import { PotatoApiError } from "../errors";
|
|
6
6
|
import { generateFieldDeclarationsSource } from "../importer";
|
|
7
7
|
import { readTolokaState } from "../state";
|
|
@@ -14,6 +14,14 @@ import type { ProjectSource } from "./source";
|
|
|
14
14
|
const RAW_UPLOAD_FIELD_NAME = "uploaded";
|
|
15
15
|
const DATASET_ID_PATTERN =
|
|
16
16
|
/^ds\.[0-9a-f]{8}-[0-9a-f]{4}-7[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$/iu;
|
|
17
|
+
const FINALIZE_UPLOAD_ATTEMPTS = 3;
|
|
18
|
+
|
|
19
|
+
class DatasetUploadError extends Error {
|
|
20
|
+
constructor(message: string, cause: unknown) {
|
|
21
|
+
super(message, { cause });
|
|
22
|
+
this.name = "DatasetUploadError";
|
|
23
|
+
}
|
|
24
|
+
}
|
|
17
25
|
|
|
18
26
|
const canonicalDatasetId = (token: string): string | undefined =>
|
|
19
27
|
DATASET_ID_PATTERN.test(token) ? token.toLowerCase() : undefined;
|
|
@@ -22,6 +30,16 @@ type UploadFieldDeclaration = NonNullable<
|
|
|
22
30
|
Parameters<PotatoClient["datasets"]["startUpload"]>[0]["fields"]
|
|
23
31
|
>[number];
|
|
24
32
|
|
|
33
|
+
type DatasetUploadFormat = NonNullable<
|
|
34
|
+
Parameters<PotatoClient["datasets"]["startUpload"]>[0]["format"]
|
|
35
|
+
>;
|
|
36
|
+
|
|
37
|
+
const DATASET_UPLOAD_CONTENT_TYPES: Record<DatasetUploadFormat, string> = {
|
|
38
|
+
json: "application/json",
|
|
39
|
+
jsonl: "application/x-ndjson",
|
|
40
|
+
csv: "text/csv",
|
|
41
|
+
};
|
|
42
|
+
|
|
25
43
|
export type UploadDatasetOptions = {
|
|
26
44
|
readonly sourceRoot: string;
|
|
27
45
|
readonly datasetName: string;
|
|
@@ -201,29 +219,138 @@ const resolveLiveDataset = async (
|
|
|
201
219
|
return { datasetId: created.id, name: created.name, nameIsUnique: true };
|
|
202
220
|
};
|
|
203
221
|
|
|
204
|
-
const
|
|
205
|
-
const
|
|
206
|
-
|
|
207
|
-
|
|
208
|
-
|
|
209
|
-
|
|
210
|
-
|
|
222
|
+
const resolveDatasetUploadFormat = (filename: string): DatasetUploadFormat => {
|
|
223
|
+
const normalizedFilename = filename.toLowerCase();
|
|
224
|
+
|
|
225
|
+
if (normalizedFilename.endsWith(".json")) return "json";
|
|
226
|
+
if (normalizedFilename.endsWith(".jsonl")) return "jsonl";
|
|
227
|
+
if (normalizedFilename.endsWith(".csv")) return "csv";
|
|
228
|
+
|
|
229
|
+
assert(false, `${filename} must have a .json, .jsonl, or .csv extension`);
|
|
230
|
+
};
|
|
231
|
+
|
|
232
|
+
const readDatasetUpload = async (filePath: string) => {
|
|
233
|
+
const filename = basename(filePath);
|
|
234
|
+
const format = resolveDatasetUploadFormat(filename);
|
|
235
|
+
const payload = await readFile(filePath, "utf8");
|
|
236
|
+
|
|
237
|
+
if (format === "jsonl") {
|
|
238
|
+
for (const { line, lineNumber } of payload
|
|
239
|
+
.split(/\r?\n/u)
|
|
240
|
+
.map((line, index) => ({ line, lineNumber: index + 1 }))
|
|
241
|
+
.filter(({ line }) => line.length > 0)) {
|
|
211
242
|
let value: unknown;
|
|
212
243
|
try {
|
|
213
244
|
value = JSON.parse(line);
|
|
214
245
|
} catch {
|
|
215
|
-
assert(false, `${
|
|
246
|
+
assert(false, `${filename}:${lineNumber} is not valid JSON`);
|
|
216
247
|
}
|
|
217
|
-
assert(isJsonObject(value), `${
|
|
218
|
-
|
|
248
|
+
assert(isJsonObject(value), `${filename}:${lineNumber} must contain a JSON object`);
|
|
249
|
+
}
|
|
250
|
+
}
|
|
251
|
+
|
|
252
|
+
return { payload, format, contentType: DATASET_UPLOAD_CONTENT_TYPES[format] };
|
|
253
|
+
};
|
|
254
|
+
|
|
255
|
+
type StartedDatasetUpload = { readonly uploadUrl: string; readonly job: { readonly id: UploadId } };
|
|
256
|
+
|
|
257
|
+
const cancelStartedUpload = async (
|
|
258
|
+
client: PotatoClient,
|
|
259
|
+
uploadId: StartedDatasetUpload["job"]["id"],
|
|
260
|
+
error: unknown,
|
|
261
|
+
): Promise<never> => {
|
|
262
|
+
const reason = error instanceof Error ? error.message : String(error);
|
|
263
|
+
|
|
264
|
+
try {
|
|
265
|
+
await client.datasets.cancelUpload({ uploadId });
|
|
266
|
+
} catch (cancelError) {
|
|
267
|
+
const cancelReason = cancelError instanceof Error ? cancelError.message : String(cancelError);
|
|
268
|
+
throw new AggregateError(
|
|
269
|
+
[error, cancelError],
|
|
270
|
+
`Dataset upload ${uploadId} failed and could not be canceled. Upload error: ${reason}. Cancellation error: ${cancelReason}.`,
|
|
271
|
+
);
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
throw new DatasetUploadError(
|
|
275
|
+
`Dataset upload ${uploadId} failed and was canceled: ${reason}`,
|
|
276
|
+
error,
|
|
277
|
+
);
|
|
278
|
+
};
|
|
279
|
+
|
|
280
|
+
const transferAndFinalizeUpload = async ({
|
|
281
|
+
client,
|
|
282
|
+
started,
|
|
283
|
+
payload,
|
|
284
|
+
contentType,
|
|
285
|
+
fetch,
|
|
286
|
+
retryIntervalMs,
|
|
287
|
+
}: {
|
|
288
|
+
client: PotatoClient;
|
|
289
|
+
started: StartedDatasetUpload;
|
|
290
|
+
payload: string;
|
|
291
|
+
contentType: string;
|
|
292
|
+
fetch: typeof globalThis.fetch;
|
|
293
|
+
retryIntervalMs: number;
|
|
294
|
+
}): Promise<void> => {
|
|
295
|
+
let uploadResponse: Response;
|
|
296
|
+
try {
|
|
297
|
+
uploadResponse = await fetch(started.uploadUrl, {
|
|
298
|
+
method: "PUT",
|
|
299
|
+
headers: {
|
|
300
|
+
"Content-Type": contentType,
|
|
301
|
+
"x-ms-blob-type": "BlockBlob",
|
|
302
|
+
},
|
|
303
|
+
body: payload,
|
|
219
304
|
});
|
|
305
|
+
} catch (error) {
|
|
306
|
+
return cancelStartedUpload(client, started.job.id, error);
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
if (!uploadResponse.ok) {
|
|
310
|
+
return cancelStartedUpload(
|
|
311
|
+
client,
|
|
312
|
+
started.job.id,
|
|
313
|
+
new DatasetUploadError(
|
|
314
|
+
`Dataset upload failed with HTTP ${uploadResponse.status}.`,
|
|
315
|
+
undefined,
|
|
316
|
+
),
|
|
317
|
+
);
|
|
318
|
+
}
|
|
319
|
+
|
|
320
|
+
let transientFailures = 0;
|
|
321
|
+
while (true) {
|
|
322
|
+
try {
|
|
323
|
+
await client.datasets.finalizeUpload({ uploadId: started.job.id });
|
|
324
|
+
return;
|
|
325
|
+
} catch (error) {
|
|
326
|
+
if (!(error instanceof PotatoApiError && error.status === 503)) {
|
|
327
|
+
return cancelStartedUpload(client, started.job.id, error);
|
|
328
|
+
}
|
|
329
|
+
|
|
330
|
+
transientFailures += 1;
|
|
331
|
+
if (transientFailures === FINALIZE_UPLOAD_ATTEMPTS) {
|
|
332
|
+
return cancelStartedUpload(
|
|
333
|
+
client,
|
|
334
|
+
started.job.id,
|
|
335
|
+
new DatasetUploadError(
|
|
336
|
+
`Storage remained unavailable after ${FINALIZE_UPLOAD_ATTEMPTS} finalization attempts.`,
|
|
337
|
+
error,
|
|
338
|
+
),
|
|
339
|
+
);
|
|
340
|
+
}
|
|
341
|
+
|
|
342
|
+
const { promise, resolve } = Promise.withResolvers<void>();
|
|
343
|
+
setTimeout(resolve, retryIntervalMs);
|
|
344
|
+
await promise;
|
|
345
|
+
}
|
|
346
|
+
}
|
|
220
347
|
};
|
|
221
348
|
|
|
222
349
|
export async function uploadDataset(
|
|
223
350
|
client: PotatoClient,
|
|
224
351
|
options: UploadDatasetOptions,
|
|
225
352
|
): Promise<UploadDatasetResult> {
|
|
226
|
-
const
|
|
353
|
+
const { payload, format, contentType } = await readDatasetUpload(options.filePath);
|
|
227
354
|
const resolved = await resolveLiveDataset(client, options, true);
|
|
228
355
|
const datasetId = resolved.datasetId;
|
|
229
356
|
|
|
@@ -236,25 +363,21 @@ export async function uploadDataset(
|
|
|
236
363
|
if (!fieldsByName.has(field.name)) fieldsByName.set(field.name, field);
|
|
237
364
|
}
|
|
238
365
|
const fields = [...fieldsByName.values()];
|
|
239
|
-
const payload = JSON.stringify(rows);
|
|
240
|
-
const payloadBytes = new TextEncoder().encode(payload);
|
|
241
366
|
const started = await client.datasets.startUpload({
|
|
242
367
|
datasetId,
|
|
243
368
|
filename: basename(options.filePath),
|
|
244
|
-
format
|
|
245
|
-
declaredSizeBytes:
|
|
369
|
+
format,
|
|
370
|
+
declaredSizeBytes: new TextEncoder().encode(payload).byteLength,
|
|
246
371
|
fields,
|
|
247
372
|
});
|
|
248
|
-
|
|
249
|
-
|
|
250
|
-
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
373
|
+
await transferAndFinalizeUpload({
|
|
374
|
+
client,
|
|
375
|
+
started,
|
|
376
|
+
payload,
|
|
377
|
+
contentType,
|
|
378
|
+
fetch: options.fetch ?? globalThis.fetch,
|
|
379
|
+
retryIntervalMs: options.waitFor?.intervalMs ?? 1000,
|
|
255
380
|
});
|
|
256
|
-
assert(uploadResponse.ok, `Dataset upload failed with HTTP ${uploadResponse.status}.`);
|
|
257
|
-
await client.datasets.finalizeUpload({ uploadId: started.job.id });
|
|
258
381
|
const completed = await client.waitFor(
|
|
259
382
|
{ type: "datasetUpload", uploadId: started.job.id },
|
|
260
383
|
options.waitFor,
|