@toloka-platform/sdk 0.2.0-preview.3 → 0.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.
Files changed (75) hide show
  1. package/README.md +5 -6
  2. package/dist/cli/toloka.js +136 -136
  3. package/dist/cli/toloka.js.jsc +0 -0
  4. package/dist/cli/toloka.js.jsc.sha256 +1 -1
  5. package/dist/cli/toloka.js.sha256 +1 -1
  6. package/docs/cli-v2.md +3 -2
  7. package/docs/cli.md +35 -7
  8. package/docs/getting-started.md +4 -2
  9. package/docs/limitations.md +2 -2
  10. package/openapi/gen/models/baseModel.ts +1 -0
  11. package/openapi/gen/models/currentUser.ts +2 -0
  12. package/openapi/gen/models/currentUserTeamsItem.ts +6 -0
  13. package/openapi/gen/models/gistingTrainingConfiguration.ts +6 -2
  14. package/openapi/gen/models/gistingTrainingConfigurationBatchSize.ts +2 -0
  15. package/openapi/gen/models/gistingTrainingConfigurationGistTokens.ts +2 -0
  16. package/openapi/gen/models/gistingTrainingConfigurationInput.ts +6 -2
  17. package/openapi/gen/models/gistingTrainingConfigurationInputBatchSize.ts +2 -0
  18. package/openapi/gen/models/gistingTrainingConfigurationInputGistTokens.ts +2 -0
  19. package/openapi/gen/models/index.ts +9 -4
  20. package/openapi/gen/models/sftTrainingConfiguration.ts +6 -3
  21. package/openapi/gen/models/sftTrainingConfigurationBatchSize.ts +2 -0
  22. package/openapi/gen/models/sftTrainingConfigurationInput.ts +6 -3
  23. package/openapi/gen/models/sftTrainingConfigurationInputBatchSize.ts +2 -0
  24. package/openapi/gen/models/sftTrainingConfigurationInputLoraRank.ts +2 -0
  25. package/openapi/gen/models/sftTrainingConfigurationLoraRank.ts +2 -0
  26. package/package.json +2 -2
  27. package/skills/catalog.json +11 -11
  28. package/skills/toloka-build/SKILL.md +29 -18
  29. package/skills/toloka-build/references/node-semantics.md +7 -7
  30. package/skills/toloka-build/references/workflow-and-cli.md +15 -12
  31. package/skills/toloka-fine-tuning/SKILL.md +145 -62
  32. package/skills/toloka-get-started/SKILL.md +37 -21
  33. package/skills/toloka-gisting/SKILL.md +156 -66
  34. package/skills/toloka-report-issue/references/report-template.md +1 -1
  35. package/src/cli/analytics.ts +50 -23
  36. package/src/cli/auth.ts +31 -12
  37. package/src/cli/auto-update.ts +12 -6
  38. package/src/cli/check.ts +76 -29
  39. package/src/cli/cli-entry.ts +3 -2
  40. package/src/cli/cli-error.ts +43 -0
  41. package/src/cli/commands/dataset.ts +13 -3
  42. package/src/cli/commands/models.ts +6 -7
  43. package/src/cli/commands/pipeline.ts +6 -7
  44. package/src/cli/commands/project.ts +9 -16
  45. package/src/cli/commands/report.ts +6 -2
  46. package/src/cli/commands/secret.ts +5 -4
  47. package/src/cli/commands/source.ts +4 -4
  48. package/src/cli/commands/status.ts +2 -3
  49. package/src/cli/commands/training.ts +50 -17
  50. package/src/cli/config.ts +2 -2
  51. package/src/cli/context.ts +55 -17
  52. package/src/cli/credentials.ts +12 -4
  53. package/src/cli/data.ts +96 -34
  54. package/src/cli/delivery-orders.ts +18 -1
  55. package/src/cli/file-system.ts +6 -2
  56. package/src/cli/format-error.ts +45 -2
  57. package/src/cli/init.ts +13 -7
  58. package/src/cli/models.ts +43 -12
  59. package/src/cli/program.ts +5 -56
  60. package/src/cli/project-context.ts +117 -32
  61. package/src/cli/project.ts +80 -28
  62. package/src/cli/report.ts +24 -9
  63. package/src/cli/runs.ts +33 -8
  64. package/src/cli/settings.ts +13 -5
  65. package/src/cli/skill.ts +61 -22
  66. package/src/cli/source.ts +3 -2
  67. package/src/cli/terminal-io.ts +13 -6
  68. package/src/cli/trainings.ts +129 -35
  69. package/src/cli/version.ts +2 -2
  70. package/src/client.ts +3 -9
  71. package/src/transport.ts +13 -1
  72. package/openapi/gen/models/gistingTrainingConfigurationInputRegime.ts +0 -2
  73. package/openapi/gen/models/gistingTrainingConfigurationRegime.ts +0 -2
  74. package/openapi/gen/models/sftTrainingConfigurationInputRegime.ts +0 -2
  75. package/openapi/gen/models/sftTrainingConfigurationRegime.ts +0 -2
@@ -3,8 +3,6 @@ import { readFile, stat } from "node:fs/promises";
3
3
 
4
4
  import { randomUUID } from "node:crypto";
5
5
 
6
- import { assert } from "es-toolkit";
7
-
8
6
  import type {
9
7
  PotatoClient,
10
8
  Training,
@@ -14,6 +12,12 @@ import type {
14
12
  TrainingRunMetrics,
15
13
  } from "../client";
16
14
  import type { BaseModelId, TrainingConfigurationInput } from "../../openapi/gen/models";
15
+ import { assertCli, RETRY_OR_REPORT_HINT, TolokaCliError } from "./cli-error";
16
+ import type { CliErrorHint } from "./cli-error";
17
+
18
+ const ESTIMATE_FILE_HINT: CliErrorHint = {
19
+ description: "Regenerate it with toloka training cost <training> --json > <file>.",
20
+ };
17
21
 
18
22
  type TrainingMethod = TrainingConfigurationInput["method"];
19
23
  type TrainingSource = TrainingConfigurationInput["source"];
@@ -30,9 +34,12 @@ export type TrainingDraftOptions = {
30
34
  validationDatasetId?: string;
31
35
  trainingFilter?: string;
32
36
  validationFilter?: string;
33
- regime?: "low" | "medium" | "high";
37
+ loraRank?: SftConfigurationInput["loraRank"];
38
+ gistTokens?: GistingConfigurationInput["gistTokens"];
34
39
  epochs?: number;
35
- evalSteps?: number;
40
+ learningRate?: number;
41
+ batchSize?: SftConfigurationInput["batchSize"];
42
+ maxSequenceLength?: number;
36
43
  promptFilePath?: string;
37
44
  fetch?: typeof globalThis.fetch;
38
45
  };
@@ -72,6 +79,13 @@ const formatTraining = (training: Training): string =>
72
79
  "Method",
73
80
  training.configuration.method === "sft" ? "Supervised fine-tuning (SFT)" : "Prompt gisting",
74
81
  ],
82
+ training.configuration.method === "sft"
83
+ ? ["LoRA rank", String(training.configuration.loraRank)]
84
+ : ["Gist tokens", String(training.configuration.gistTokens)],
85
+ ["Epochs", String(training.configuration.epochs)],
86
+ ["Learning rate", String(training.configuration.learningRate)],
87
+ ["Batch size", String(training.configuration.batchSize)],
88
+ ["Max sequence length", String(training.configuration.maxSequenceLength)],
75
89
  ["Output Model", training.modelId ?? "-"],
76
90
  ["Archived at", training.archivedAt ?? "-"],
77
91
  ["Created at", training.createdAt],
@@ -115,27 +129,32 @@ type ReadyEstimateFile = {
115
129
  };
116
130
 
117
131
  const parseReadyEstimateFile = (value: unknown): ReadyEstimateFile => {
118
- assert(
132
+ assertCli(
119
133
  typeof value === "object" && value !== null && !Array.isArray(value),
120
134
  "The estimate file must contain a JSON object.",
135
+ { hint: ESTIMATE_FILE_HINT },
121
136
  );
122
- assert(
137
+ assertCli(
123
138
  "status" in value && value.status === "ready",
124
139
  "The estimate file is not a ready Training Cost Estimate.",
140
+ { hint: ESTIMATE_FILE_HINT },
125
141
  );
126
- assert(
142
+ assertCli(
127
143
  "trainingId" in value && typeof value.trainingId === "string",
128
144
  "The estimate file has no Training ID.",
145
+ { hint: ESTIMATE_FILE_HINT },
129
146
  );
130
- assert(
147
+ assertCli(
131
148
  "inputSetId" in value && typeof value.inputSetId === "string",
132
149
  "The estimate file has no Input Set ID.",
150
+ { hint: ESTIMATE_FILE_HINT },
133
151
  );
134
- assert(
152
+ assertCli(
135
153
  "fingerprint" in value &&
136
154
  typeof value.fingerprint === "string" &&
137
155
  /^[0-9a-f]{64}$/.test(value.fingerprint),
138
156
  "The estimate file has no valid fingerprint.",
157
+ { hint: ESTIMATE_FILE_HINT },
139
158
  );
140
159
 
141
160
  return {
@@ -171,9 +190,10 @@ const resolveTraining = async (
171
190
  ): Promise<Training> => {
172
191
  if (nameOrId.startsWith("trg.")) {
173
192
  const training = await client.trainings.get({ trainingId: nameOrId });
174
- assert(
193
+ assertCli(
175
194
  training.projectId === projectId,
176
195
  `Training ${nameOrId} was not found in project ${projectId}.`,
196
+ { hint: { description: "Run toloka training list; a Training belongs to one project." } },
177
197
  );
178
198
  return training;
179
199
  }
@@ -182,7 +202,9 @@ const resolveTraining = async (
182
202
  for await (const training of client.trainings.listAll({ projectId, limit: 100 })) {
183
203
  if (training.name === nameOrId) matches.push(training);
184
204
  }
185
- assert(matches.length === 1, `Training ${nameOrId} was not found or is ambiguous.`);
205
+ assertCli(matches.length === 1, `Training ${nameOrId} was not found or is ambiguous.`, {
206
+ hint: { description: "Run toloka training list and pass the training id (trg.…)." },
207
+ });
186
208
  return matches[0]!;
187
209
  };
188
210
 
@@ -199,12 +221,18 @@ const resolveSelection = async (
199
221
  datasetId: string,
200
222
  filter: string | undefined,
201
223
  ): Promise<TrainingSelection> => {
202
- assert(datasetId, "A Dataset is required when specifying a filter.");
224
+ assertCli(datasetId, "A Dataset is required when specifying a filter.", {
225
+ hint: {
226
+ description:
227
+ "Pass --dataset together with --train-filter, and --validation-dataset together with --validation-filter.",
228
+ },
229
+ });
203
230
  if (filter === undefined) return { datasetId };
204
231
  const separator = filter.indexOf("=");
205
- assert(
232
+ assertCli(
206
233
  separator > 0 && separator < filter.length - 1,
207
234
  "Dataset filters must use FIELD=VALUE syntax.",
235
+ { hint: { description: "Write the filter as field=value, for example split=train." } },
208
236
  );
209
237
  const fieldName = filter.slice(0, separator);
210
238
  const rawValue = filter.slice(separator + 1);
@@ -213,9 +241,10 @@ const resolveSelection = async (
213
241
  if (field.name === fieldName) matches.push(field);
214
242
  }
215
243
  const [field] = matches;
216
- assert(
244
+ assertCli(
217
245
  field && matches.length === 1,
218
246
  `Dataset field ${fieldName} was not found or is ambiguous.`,
247
+ { hint: { description: "Run toloka dataset fields <dataset> to see the field names." } },
219
248
  );
220
249
  return {
221
250
  datasetId,
@@ -240,14 +269,17 @@ const sourceFromOptions = (
240
269
  options: Pick<TrainingDraftOptions, "baseModelId" | "modelVersionId">,
241
270
  fallback?: TrainingSource,
242
271
  ): TrainingSource => {
243
- assert(
272
+ assertCli(
244
273
  !(options.baseModelId && options.modelVersionId),
245
274
  "Choose exactly one of --base-model and --model-version.",
275
+ { hint: { disabled: true } },
246
276
  );
247
277
  if (options.baseModelId) return { type: "base_model", baseModelId: options.baseModelId };
248
278
  if (options.modelVersionId)
249
279
  return { type: "model_version", modelVersionId: options.modelVersionId };
250
- assert(fallback, "Choose exactly one of --base-model and --model-version.");
280
+ assertCli(fallback, "Choose exactly one of --base-model and --model-version.", {
281
+ hint: { disabled: true },
282
+ });
251
283
  return fallback;
252
284
  };
253
285
 
@@ -258,20 +290,29 @@ const uploadPromptFile = async (
258
290
  fetchImplementation: typeof globalThis.fetch,
259
291
  ): Promise<string> => {
260
292
  const metadata = await stat(filePath);
261
- assert(metadata.isFile(), "--prompt-file must point to a file.");
262
- assert(metadata.size > 0, "The gisting prompt file is empty.");
263
- assert(
293
+ assertCli(metadata.isFile(), "--prompt-file must point to a file.", {
294
+ hint: { description: "Pass the absolute path of the prompt text file." },
295
+ });
296
+ assertCli(metadata.size > 0, "The gisting prompt file is empty.", {
297
+ hint: { description: "Put the full prompt text into the file before creating the Training." },
298
+ });
299
+ assertCli(
264
300
  metadata.size <= GISTING_PROMPT_MAX_BYTES,
265
301
  "The gisting prompt file exceeds the 256 KiB limit.",
302
+ { hint: { description: "Shorten the prompt to under 256 KiB." } },
266
303
  );
267
304
  const payload = await readFile(filePath);
268
305
  let prompt: string;
269
306
  try {
270
307
  prompt = new TextDecoder("utf-8", { fatal: true }).decode(payload);
271
308
  } catch {
272
- throw new Error("The gisting prompt file must contain valid UTF-8 text.");
309
+ throw new TolokaCliError("The gisting prompt file must contain valid UTF-8 text.", {
310
+ hint: { description: "Save the prompt file as UTF-8 text." },
311
+ });
273
312
  }
274
- assert(prompt.trim().length > 0, "The gisting prompt file is empty.");
313
+ assertCli(prompt.trim().length > 0, "The gisting prompt file is empty.", {
314
+ hint: { description: "Put the full prompt text into the file before creating the Training." },
315
+ });
275
316
  const started = await client.files.startUpload({
276
317
  projectId,
277
318
  filename: basename(filePath),
@@ -283,7 +324,9 @@ const uploadPromptFile = async (
283
324
  headers: { "x-ms-blob-type": "BlockBlob", "Content-Type": "text/plain; charset=utf-8" },
284
325
  body: payload,
285
326
  });
286
- assert(response.ok, `Failed to upload gisting prompt: HTTP ${response.status}.`);
327
+ assertCli(response.ok, `Failed to upload gisting prompt: HTTP ${response.status}.`, {
328
+ hint: RETRY_OR_REPORT_HINT,
329
+ });
287
330
  return (await client.files.finalizeUpload({ projectId, fileId: started.file.id })).id;
288
331
  } catch (error) {
289
332
  await client.files.delete({ projectId, fileId: started.file.id }).catch(() => undefined);
@@ -301,7 +344,9 @@ const resolveSelections = async ({
301
344
  options: TrainingDraftOptions;
302
345
  }): Promise<ResolvedSelections> => {
303
346
  const trainingDatasetId = options.datasetId ?? current?.training.datasetId;
304
- assert(trainingDatasetId, "--dataset is required.");
347
+ assertCli(trainingDatasetId, "--dataset is required.", {
348
+ hint: { description: "Pass --dataset <dataset-id>; toloka dataset list shows the ids." },
349
+ });
305
350
  const training =
306
351
  options.datasetId === undefined && options.trainingFilter === undefined && current
307
352
  ? current.training
@@ -317,6 +362,14 @@ const resolveSelections = async ({
317
362
  return { training, validation };
318
363
  };
319
364
 
365
+ const sharedHyperparameters = (options: TrainingDraftOptions) => ({
366
+ ...(options.learningRate === undefined ? {} : { learningRate: options.learningRate }),
367
+ ...(options.batchSize === undefined ? {} : { batchSize: options.batchSize }),
368
+ ...(options.maxSequenceLength === undefined
369
+ ? {}
370
+ : { maxSequenceLength: options.maxSequenceLength }),
371
+ });
372
+
320
373
  const buildSftConfiguration = ({
321
374
  source,
322
375
  selections,
@@ -328,15 +381,28 @@ const buildSftConfiguration = ({
328
381
  current: SftConfigurationInput | undefined;
329
382
  options: TrainingDraftOptions;
330
383
  }): SftConfigurationInput => {
331
- assert(options.promptFilePath === undefined, "Gisting options cannot be used with SFT.");
384
+ assertCli(options.promptFilePath === undefined, "--prompt-file applies to gisting only.", {
385
+ hint: {
386
+ description: "Pass --method gisting to use --prompt-file, or omit --prompt-file for SFT.",
387
+ },
388
+ });
389
+ assertCli(
390
+ options.gistTokens === undefined,
391
+ "--gist-tokens applies to gisting; SFT takes --lora-rank.",
392
+ {
393
+ hint: {
394
+ description: "Use --lora-rank for SFT, or pass --method gisting to use --gist-tokens.",
395
+ },
396
+ },
397
+ );
332
398
  return {
333
399
  ...current,
334
400
  method: "sft",
335
401
  source,
336
402
  ...selections,
337
- ...(options.regime === undefined ? {} : { regime: options.regime }),
403
+ ...(options.loraRank === undefined ? {} : { loraRank: options.loraRank }),
338
404
  ...(options.epochs === undefined ? {} : { epochs: options.epochs }),
339
- ...(options.evalSteps === undefined ? {} : { evalSteps: options.evalSteps }),
405
+ ...sharedHyperparameters(options),
340
406
  };
341
407
  };
342
408
 
@@ -355,7 +421,15 @@ const buildGistingConfiguration = async ({
355
421
  current: GistingConfigurationInput | undefined;
356
422
  options: TrainingDraftOptions;
357
423
  }): Promise<GistingConfigurationInput> => {
358
- assert(options.evalSteps === undefined, "SFT options cannot be used with gisting.");
424
+ assertCli(
425
+ options.loraRank === undefined,
426
+ "--lora-rank applies to SFT; gisting takes --gist-tokens.",
427
+ {
428
+ hint: {
429
+ description: "Use --gist-tokens for gisting, or omit --method gisting to create SFT.",
430
+ },
431
+ },
432
+ );
359
433
  const promptFileId = options.promptFilePath
360
434
  ? await uploadPromptFile(
361
435
  client,
@@ -364,15 +438,18 @@ const buildGistingConfiguration = async ({
364
438
  options.fetch ?? globalThis.fetch,
365
439
  )
366
440
  : current?.promptFileId;
367
- assert(promptFileId, "--prompt-file is required for gisting.");
441
+ assertCli(promptFileId, "--prompt-file is required for gisting.", {
442
+ hint: { description: "Pass --prompt-file <absolute-path> with the prompt text." },
443
+ });
368
444
  return {
369
445
  ...current,
370
446
  method: "gisting",
371
447
  source,
372
448
  ...selections,
373
449
  promptFileId,
374
- ...(options.regime === undefined ? {} : { regime: options.regime }),
450
+ ...(options.gistTokens === undefined ? {} : { gistTokens: options.gistTokens }),
375
451
  ...(options.epochs === undefined ? {} : { epochs: options.epochs }),
452
+ ...sharedHyperparameters(options),
376
453
  };
377
454
  };
378
455
 
@@ -453,10 +530,13 @@ export const createTraining = async (
453
530
  },
454
531
  io: TrainingIo,
455
532
  ): Promise<void> => {
456
- assert(input.datasetId, "--dataset is required.");
457
- assert(
533
+ assertCli(input.datasetId, "--dataset is required.", {
534
+ hint: { description: "Pass --dataset <dataset-id>; toloka dataset list shows the ids." },
535
+ });
536
+ assertCli(
458
537
  !input.validationFilter || input.validationDatasetId,
459
538
  "--validation-filter requires --validation-dataset.",
539
+ { hint: { disabled: true } },
460
540
  );
461
541
 
462
542
  const configuration = await buildTrainingConfiguration({
@@ -526,6 +606,9 @@ export const estimateTrainingCost = async (
526
606
  io: TrainingIo,
527
607
  ): Promise<void> => {
528
608
  const { estimate } = await prepareTrainingCostEstimate(client, input);
609
+ assertCli(estimate.status === "ready", formatCostEstimate(estimate), {
610
+ hint: { description: "Fix the blocked issues above, then run toloka training cost again." },
611
+ });
529
612
  writeOutput(io, input.json, estimate, formatCostEstimate(estimate));
530
613
  };
531
614
 
@@ -549,10 +632,19 @@ export const createTrainingRun = async (
549
632
  projectId: input.projectId,
550
633
  nameOrId: input.nameOrId,
551
634
  });
552
- assert(prepared.estimate.status === "ready", formatCostEstimate(prepared.estimate));
635
+ assertCli(prepared.estimate.status === "ready", formatCostEstimate(prepared.estimate), {
636
+ hint: {
637
+ description: "Fix the blocked issues above, then run toloka training cost again.",
638
+ },
639
+ });
553
640
  return parseReadyEstimateFile(prepared.estimate);
554
641
  })();
555
- assert(estimate.trainingId === training.id, "The estimate file belongs to another Training.");
642
+ assertCli(estimate.trainingId === training.id, "The estimate file belongs to another Training.", {
643
+ hint: {
644
+ description:
645
+ "Run toloka training cost <training> --json > <file> for this Training, get its maximumCharge approved, then re-run with that file.",
646
+ },
647
+ });
556
648
  const run = await client.trainings.createRun({
557
649
  trainingId: training.id,
558
650
  inputSetId: estimate.inputSetId,
@@ -657,8 +749,10 @@ const formatMetricValue = (point: {
657
749
  };
658
750
 
659
751
  const METRICS_UNAVAILABLE: Record<"not_configured" | "unresolved", string> = {
660
- not_configured: "This Training Run has no metrics: it started before metrics were available, or the server records none.",
661
- unresolved: "Metrics are temporarily unreachable. The Training Run itself is unaffected; try again later.",
752
+ not_configured:
753
+ "This Training Run has no metrics: it started before metrics were available, or the server records none.",
754
+ unresolved:
755
+ "Metrics are temporarily unreachable. The Training Run itself is unaffected; try again later.",
662
756
  };
663
757
 
664
758
  const formatTrainingRunMetrics = (metrics: TrainingRunMetrics): string => {
@@ -2,8 +2,8 @@ import { realpathSync } from "node:fs";
2
2
  import { homedir } from "node:os";
3
3
  import { dirname, join, resolve, sep } from "node:path";
4
4
 
5
- import { assert } from "es-toolkit";
6
5
  import { SDK_VERSION } from "./sdk-version";
6
+ import { assertCli, REPORT_HINT } from "./cli-error";
7
7
 
8
8
  export type CliInstallChannel = "global-registry" | "linked" | "project-local";
9
9
 
@@ -36,7 +36,7 @@ const classifyInstallChannel = (
36
36
  };
37
37
 
38
38
  export const cliVersionInfo = (entryPath = process.argv[1], options: VersionInfoOptions = {}) => {
39
- assert(entryPath, "CLI entry path is unavailable");
39
+ assertCli(entryPath, "CLI entry path is unavailable", { hint: REPORT_HINT });
40
40
  const globalBinDirectory =
41
41
  options.globalBinDirectory ??
42
42
  resolve(process.env.BUN_INSTALL ?? join(homedir(), ".bun"), "bin");
package/src/client.ts CHANGED
@@ -228,12 +228,6 @@ export type WaitForTarget =
228
228
  const createClientImplementation = (config: ClientConfig) => {
229
229
  const requestOptions = createRequestOptions(config.baseUrl, config.apiKey, config.fetch);
230
230
 
231
- const emptyJsonRequestOptions = (): RequestInit => {
232
- const headers = new Headers(requestOptions.headers);
233
- headers.set("Content-Type", "application/json");
234
- return { ...requestOptions, headers, body: "{}" };
235
- };
236
-
237
231
  function waitFor(
238
232
  target: DatasetUploadWaitTarget,
239
233
  options?: WaitForOptions,
@@ -564,9 +558,9 @@ const createClientImplementation = (config: ClientConfig) => {
564
558
  startUpload: ({ datasetId, ...body }: { datasetId: DatasetId } & CreateDatasetUploadBody) =>
565
559
  wire.createDatasetUpload(datasetId, body, requestOptions),
566
560
  finalizeUpload: ({ uploadId }: { uploadId: UploadId }) =>
567
- wire.finalizeDatasetUpload(uploadId, emptyJsonRequestOptions()),
561
+ wire.finalizeDatasetUpload(uploadId, requestOptions),
568
562
  cancelUpload: ({ uploadId }: { uploadId: UploadId }) =>
569
- wire.cancelDatasetUpload(uploadId, emptyJsonRequestOptions()),
563
+ wire.cancelDatasetUpload(uploadId, requestOptions),
570
564
  getUpload: ({ uploadId }: { uploadId: UploadId }) =>
571
565
  wire.getDatasetUpload(uploadId, requestOptions),
572
566
  startExport: ({ datasetId, ...body }: { datasetId: DatasetId } & CreateDatasetExportBody) =>
@@ -577,7 +571,7 @@ const createClientImplementation = (config: ClientConfig) => {
577
571
 
578
572
  const runs = {
579
573
  start: ({ pipelineId }: { pipelineId: PipelineId }) =>
580
- wire.createPipelineRun(pipelineId, emptyJsonRequestOptions()),
574
+ wire.createPipelineRun(pipelineId, requestOptions),
581
575
  get: ({ runId }: { runId: RunId }) => wire.getPipelineRun(runId, requestOptions),
582
576
  cancel: ({ runId }: { runId: RunId }) => wire.cancelPipelineRun(runId, requestOptions),
583
577
  listPage: ({ pipelineId, ...params }: { pipelineId: PipelineId } & ListPipelineRunsParams) =>
package/src/transport.ts CHANGED
@@ -25,6 +25,15 @@ export const createRequestOptions = (
25
25
  };
26
26
  };
27
27
 
28
+ const prepareRequestInit = (requestOptions: RequestInit, headers: Headers): RequestInit => {
29
+ const method = (requestOptions.method ?? "GET").toUpperCase();
30
+ if (method === "GET" || method === "HEAD" || requestOptions.body !== undefined) {
31
+ return { ...requestOptions, headers };
32
+ }
33
+ headers.set("Content-Type", "application/json");
34
+ return { ...requestOptions, headers, body: "{}" };
35
+ };
36
+
28
37
  export function potatoFetch<T>(url: string, options: TransportRequestInit): Promise<T>;
29
38
  export async function potatoFetch(url: string, options: TransportRequestInit): Promise<unknown> {
30
39
  const { fetch: fetchImplementation = globalThis.fetch, ...requestOptions } = options;
@@ -33,7 +42,10 @@ export async function potatoFetch(url: string, options: TransportRequestInit): P
33
42
  assert(baseUrl, "The SDK base URL is missing");
34
43
  headers.delete(BASE_URL_HEADER);
35
44
 
36
- const response = await fetchImplementation(new URL(url, baseUrl), { ...requestOptions, headers });
45
+ const response = await fetchImplementation(
46
+ new URL(url, baseUrl),
47
+ prepareRequestInit(requestOptions, headers),
48
+ );
37
49
  const text = await response.text();
38
50
 
39
51
  if (!response.ok) {
@@ -1,2 +0,0 @@
1
-
2
- export type GistingTrainingConfigurationInputRegime = 'low' | 'medium' | 'high';
@@ -1,2 +0,0 @@
1
-
2
- export type GistingTrainingConfigurationRegime = 'low' | 'medium' | 'high';
@@ -1,2 +0,0 @@
1
-
2
- export type SftTrainingConfigurationInputRegime = 'low' | 'medium' | 'high';
@@ -1,2 +0,0 @@
1
-
2
- export type SftTrainingConfigurationRegime = 'low' | 'medium' | 'high';