@toloka-platform/sdk 0.2.0-preview.2 → 0.2.0-preview.4

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 (110) hide show
  1. package/README.md +10 -4
  2. package/dist/cli/toloka.js +162 -154
  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/authoring.md +18 -1
  7. package/docs/cli-v2.md +6 -4
  8. package/docs/cli.md +58 -11
  9. package/docs/getting-started.md +3 -3
  10. package/docs/limitations.md +1 -1
  11. package/docs/testing.md +1 -1
  12. package/docs/troubleshooting.md +1 -1
  13. package/examples/face-image-collection/README.md +2 -2
  14. package/openapi/gen/api.ts +78 -0
  15. package/openapi/gen/models/baseModel.ts +1 -0
  16. package/openapi/gen/models/checkReport.ts +2 -1
  17. package/openapi/gen/models/checkReportAnyOfTwoseven.ts +5 -0
  18. package/openapi/gen/models/checkReportAnyOfTwosevenResultsItem.ts +6 -0
  19. package/openapi/gen/models/checkReportAnyOfTwosevenResultsItemVerdict.ts +2 -0
  20. package/openapi/gen/models/currentUser.ts +3 -0
  21. package/openapi/gen/models/currentUserTeamsItem.ts +6 -0
  22. package/openapi/gen/models/deliveryOrder.ts +9 -0
  23. package/openapi/gen/models/deliveryOrderClientName.ts +2 -0
  24. package/openapi/gen/models/getTrainingRunMetricHistoryParams.ts +4 -0
  25. package/openapi/gen/models/gistingTrainingConfiguration.ts +6 -2
  26. package/openapi/gen/models/gistingTrainingConfigurationBatchSize.ts +2 -0
  27. package/openapi/gen/models/gistingTrainingConfigurationGistTokens.ts +2 -0
  28. package/openapi/gen/models/gistingTrainingConfigurationInput.ts +6 -2
  29. package/openapi/gen/models/gistingTrainingConfigurationInputBatchSize.ts +2 -0
  30. package/openapi/gen/models/gistingTrainingConfigurationInputGistTokens.ts +2 -0
  31. package/openapi/gen/models/index.ts +28 -4
  32. package/openapi/gen/models/listDeliveryOrders200.ts +5 -0
  33. package/openapi/gen/models/sftTrainingConfiguration.ts +6 -3
  34. package/openapi/gen/models/sftTrainingConfigurationBatchSize.ts +2 -0
  35. package/openapi/gen/models/sftTrainingConfigurationInput.ts +6 -3
  36. package/openapi/gen/models/sftTrainingConfigurationInputBatchSize.ts +2 -0
  37. package/openapi/gen/models/sftTrainingConfigurationInputLoraRank.ts +2 -0
  38. package/openapi/gen/models/sftTrainingConfigurationLoraRank.ts +2 -0
  39. package/openapi/gen/models/trainingRunMetricHistory.ts +9 -0
  40. package/openapi/gen/models/trainingRunMetricHistoryMetric.ts +2 -0
  41. package/openapi/gen/models/trainingRunMetricHistoryPointsItem.ts +9 -0
  42. package/openapi/gen/models/trainingRunMetricHistoryPointsItemNonFinite.ts +2 -0
  43. package/openapi/gen/models/trainingRunMetricHistoryPointsItemValue.ts +2 -0
  44. package/openapi/gen/models/trainingRunMetrics.ts +9 -0
  45. package/openapi/gen/models/trainingRunMetricsMetricsItem.ts +8 -0
  46. package/openapi/gen/models/trainingRunMetricsMetricsItemLatest.ts +9 -0
  47. package/openapi/gen/models/trainingRunMetricsMetricsItemLatestNonFinite.ts +2 -0
  48. package/openapi/gen/models/trainingRunMetricsMetricsItemLatestValue.ts +2 -0
  49. package/openapi/gen/models/trainingRunMetricsMetricsItemMetric.ts +2 -0
  50. package/openapi/gen/models/trainingRunMetricsUnavailableReason.ts +2 -0
  51. package/package.json +2 -2
  52. package/skills/catalog.json +55 -7
  53. package/skills/toloka-build/SKILL.md +35 -21
  54. package/skills/toloka-build/references/node-semantics.md +13 -11
  55. package/skills/toloka-build/references/workflow-and-cli.md +31 -20
  56. package/skills/toloka-fine-tuning/SKILL.md +155 -0
  57. package/skills/toloka-get-started/SKILL.md +70 -0
  58. package/skills/toloka-gisting/SKILL.md +166 -0
  59. package/skills/toloka-report-issue/references/report-template.md +1 -1
  60. package/src/authoring/index.ts +14 -0
  61. package/src/checks/task-ui-typecheck.ts +12 -10
  62. package/src/cli/analytics.ts +88 -15
  63. package/src/cli/auth.ts +93 -21
  64. package/src/cli/auto-update.ts +36 -39
  65. package/src/cli/check.ts +88 -39
  66. package/src/cli/cli-entry.ts +3 -2
  67. package/src/cli/cli-error.ts +43 -0
  68. package/src/cli/commands/auth.ts +89 -0
  69. package/src/cli/commands/config.ts +28 -0
  70. package/src/cli/commands/dataset.ts +85 -0
  71. package/src/cli/commands/delivery-order.ts +18 -0
  72. package/src/cli/commands/models.ts +121 -0
  73. package/src/cli/commands/pipeline.ts +131 -0
  74. package/src/cli/commands/project.ts +168 -0
  75. package/src/cli/commands/report.ts +43 -0
  76. package/src/cli/commands/secret.ts +40 -0
  77. package/src/cli/commands/skill.ts +36 -0
  78. package/src/cli/commands/source.ts +204 -0
  79. package/src/cli/commands/status.ts +17 -0
  80. package/src/cli/commands/training.ts +261 -0
  81. package/src/cli/commands/version.ts +14 -0
  82. package/src/cli/config.ts +7 -8
  83. package/src/cli/context.ts +251 -0
  84. package/src/cli/credentials.ts +12 -4
  85. package/src/cli/data.ts +96 -34
  86. package/src/cli/delivery-orders.ts +47 -0
  87. package/src/cli/environment.ts +26 -0
  88. package/src/cli/file-system.ts +6 -2
  89. package/src/cli/format-error.ts +45 -2
  90. package/src/cli/init.ts +14 -11
  91. package/src/cli/models.ts +43 -12
  92. package/src/cli/program.ts +120 -1347
  93. package/src/cli/project-context.ts +126 -43
  94. package/src/cli/project.ts +80 -28
  95. package/src/cli/report.ts +24 -9
  96. package/src/cli/runs.ts +33 -8
  97. package/src/cli/sdk-version.ts +38 -0
  98. package/src/cli/settings.ts +13 -5
  99. package/src/cli/skill.ts +67 -24
  100. package/src/cli/source.ts +3 -2
  101. package/src/cli/terminal-io.ts +110 -0
  102. package/src/cli/trainings.ts +242 -57
  103. package/src/cli/version.ts +3 -3
  104. package/src/client.ts +21 -9
  105. package/src/importer.ts +32 -2
  106. package/src/transport.ts +13 -1
  107. package/openapi/gen/models/gistingTrainingConfigurationInputRegime.ts +0 -2
  108. package/openapi/gen/models/gistingTrainingConfigurationRegime.ts +0 -2
  109. package/openapi/gen/models/sftTrainingConfigurationInputRegime.ts +0 -2
  110. package/openapi/gen/models/sftTrainingConfigurationRegime.ts +0 -2
@@ -1,4 +1,5 @@
1
1
  import { PotatoApiError } from "../errors";
2
+ import { formatHint, TolokaCliError } from "./cli-error";
2
3
 
3
4
  export const apiErrorDetails = (error: PotatoApiError): string | undefined => {
4
5
  const details =
@@ -10,10 +11,52 @@ export const apiErrorDetails = (error: PotatoApiError): string | undefined => {
10
11
  return details === undefined || details.length === 0 ? undefined : details;
11
12
  };
12
13
 
14
+ const apiErrorHint = (error: PotatoApiError): string | undefined => {
15
+ if (error.code === "wait_timeout") {
16
+ return "Hint: The job is still running. Check it later with its status command; commands that accept --timeout can wait longer.";
17
+ }
18
+ if (error.status === 401 || error.status === 403) {
19
+ return "Hint: Run toloka whoami to see which key and platform are in use; toloka login stores a new key.";
20
+ }
21
+ if (error.status === 404) {
22
+ return "Hint: Check the id against toloka project current and the matching list command; ids belong to one project on one platform.";
23
+ }
24
+ if (error.status === 429) {
25
+ return "Hint: The platform is rate limiting this key; wait a moment and retry.";
26
+ }
27
+ if (error.status !== undefined && error.status >= 500) {
28
+ return "Hint: The platform failed to handle the request. Retry; if it keeps failing, write a report as the toloka-report-issue skill describes and send it with toloka report <report.md>.";
29
+ }
30
+ return undefined;
31
+ };
32
+
33
+ const joinLines = (lines: ReadonlyArray<string | undefined>): string =>
34
+ lines.filter((line) => line !== undefined && line.length > 0).join("\n");
35
+
36
+ export const findPotatoApiError = (error: unknown): PotatoApiError | undefined => {
37
+ if (error instanceof PotatoApiError) return error;
38
+ if (error instanceof AggregateError) {
39
+ for (const nested of error.errors) {
40
+ const apiError = findPotatoApiError(nested);
41
+ if (apiError !== undefined) return apiError;
42
+ }
43
+ }
44
+ return error instanceof Error ? findPotatoApiError(error.cause) : undefined;
45
+ };
46
+
13
47
  export const formatCliError = (error: unknown): string => {
48
+ if (error instanceof TolokaCliError) {
49
+ const cause = findPotatoApiError(error.cause);
50
+ const causeLines =
51
+ cause === undefined
52
+ ? []
53
+ : cause.message === error.message
54
+ ? [apiErrorDetails(cause)]
55
+ : [cause.message, apiErrorDetails(cause)];
56
+ return joinLines([error.message, ...causeLines, formatHint(error.hint)]);
57
+ }
14
58
  if (error instanceof PotatoApiError) {
15
- const details = apiErrorDetails(error);
16
- return details === undefined ? error.message : `${error.message}\n${details}`;
59
+ return joinLines([error.message, apiErrorDetails(error), apiErrorHint(error)]);
17
60
  }
18
61
  return error instanceof Error ? error.message : String(error);
19
62
  };
package/src/cli/init.ts CHANGED
@@ -1,14 +1,11 @@
1
- import { assert } from "es-toolkit";
2
1
  import { lstat, mkdir, readdir, readFile, realpath, rm, rmdir, writeFile } from "node:fs/promises";
3
2
  import { dirname, join } from "node:path";
4
- import packageMetadata from "../../package.json" with { type: "json" };
5
3
  import { readTolokaState, writeTolokaState } from "../state";
6
4
  import { assertSafeDestination, entryStats, isMissingFileError } from "./file-system";
5
+ import { SDK_PACKAGE_NAME, SDK_VERSION } from "./sdk-version";
7
6
  import { SKILL_ENTRY_POINT, SKILL_INSTALL_ROOT } from "./skill";
7
+ import { assertCli } from "./cli-error";
8
8
 
9
- export const SDK_VERSION = packageMetadata.version;
10
-
11
- const SDK_PACKAGE_NAME = "@toloka-platform/sdk";
12
9
  const BOOTSTRAP_NAME = "toloka-bootstrap";
13
10
  const CLAUDE_POINTER_PATH = `.claude/skills/${BOOTSTRAP_NAME}/SKILL.md`;
14
11
  const CURSOR_POINTER_PATH = `.cursor/rules/${BOOTSTRAP_NAME}.mdc`;
@@ -109,14 +106,16 @@ const locateBlock = (source: string, markers: BlockMarkers, path: string) => {
109
106
  const beginIndex = source.indexOf(markers.begin);
110
107
  const endIndex = source.indexOf(markers.end);
111
108
  if (beginIndex === -1 && endIndex === -1) return undefined;
112
- assert(
109
+ assertCli(
113
110
  beginIndex !== -1 && endIndex !== -1 && beginIndex < endIndex,
114
111
  `Malformed Toloka block in ${path}: expected one ${markers.begin} followed by one ${markers.end}. Repair the file by hand, then re-run \`toloka init\`.`,
112
+ { hint: { disabled: true } },
115
113
  );
116
- assert(
114
+ assertCli(
117
115
  !source.includes(markers.begin, beginIndex + markers.begin.length) &&
118
116
  !source.includes(markers.end, endIndex + markers.end.length),
119
117
  `Multiple Toloka blocks in ${path}: keep exactly one ${markers.begin} ... ${markers.end} block, then re-run \`toloka init\`.`,
118
+ { hint: { disabled: true } },
120
119
  );
121
120
  return { beginIndex, endIndex: endIndex + markers.end.length };
122
121
  };
@@ -124,9 +123,10 @@ const locateBlock = (source: string, markers: BlockMarkers, path: string) => {
124
123
  const mergeBootstrap = (source: string, path: string) => {
125
124
  const current = locateBlock(source, CURRENT_BLOCK, path);
126
125
  const legacy = locateBlock(source, LEGACY_BLOCK, path);
127
- assert(
126
+ assertCli(
128
127
  current === undefined || legacy === undefined,
129
128
  `Ambiguous Toloka blocks in ${path}: both ${CURRENT_BLOCK.begin} and ${LEGACY_BLOCK.begin} are present. Keep one, then re-run \`toloka init\`.`,
129
+ { hint: { disabled: true } },
130
130
  );
131
131
  const lineEnding = source.includes("\r\n") ? "\r\n" : "\n";
132
132
  const block = BOOTSTRAP_BLOCK.replaceAll("\n", lineEnding);
@@ -149,9 +149,10 @@ const hasSdkDependency = async (targetDirectory: string) => {
149
149
  const source = await readOptionalFile(join(targetDirectory, "package.json"));
150
150
  if (source === undefined) return false;
151
151
  const manifest: unknown = JSON.parse(source);
152
- assert(
152
+ assertCli(
153
153
  typeof manifest === "object" && manifest !== null,
154
154
  "package.json must contain a JSON object.",
155
+ { hint: { description: "Fix package.json in the source root, then re-run toloka init." } },
155
156
  );
156
157
  return ["dependencies", "devDependencies"].some((key) => {
157
158
  const dependencies = Reflect.get(manifest, key);
@@ -173,9 +174,10 @@ const writeIfChanged = async (path: string, source: string) => {
173
174
  const assertRefreshablePointer = async (targetRoot: string, relativePath: string) => {
174
175
  const path = join(targetRoot, relativePath);
175
176
  const existing = await readOptionalFile(path);
176
- assert(
177
+ assertCli(
177
178
  existing === undefined || isGenerated(existing),
178
179
  `Refusing to overwrite ${path}: it carries no \`<!-- toloka:sdk-version ... -->\` marker, so it is not a pointer generated by ${SDK_PACKAGE_NAME}. Move it aside, then re-run \`toloka init\`.`,
180
+ { hint: { disabled: true } },
179
181
  );
180
182
  };
181
183
 
@@ -218,9 +220,10 @@ const bindCheckout = async (
218
220
  ): Promise<string | undefined> => {
219
221
  if (projectId === undefined) return undefined;
220
222
  const existing = await readTolokaState(targetRoot);
221
- assert(
223
+ assertCli(
222
224
  existing === undefined || existing.projectId === projectId,
223
225
  `This directory is already bound to ${existing?.projectId}.`,
226
+ { hint: { description: "Run toloka init without --project-id to keep the current binding, or remove toloka.json to bind another project." } },
224
227
  );
225
228
  if (existing !== undefined) return undefined;
226
229
  await writeTolokaState(targetRoot, { projectId, pipelines: {}, datasets: {} });
package/src/cli/models.ts CHANGED
@@ -5,9 +5,9 @@ import { isAbsolute, relative, resolve, sep } from "node:path";
5
5
  import { Readable, Transform } from "node:stream";
6
6
  import { pipeline } from "node:stream/promises";
7
7
 
8
- import { assert } from "es-toolkit";
9
8
 
10
9
  import type { BaseModel, BaseModelId, Model, ModelVersion, PotatoClient } from "../client";
10
+ import { assertCli, REPORT_HINT, RETRY_OR_REPORT_HINT } from "./cli-error";
11
11
 
12
12
  type ModelApi = Pick<PotatoClient, "baseModels" | "models">;
13
13
  type ModelIo = { readonly stdout: (text: string) => void };
@@ -46,6 +46,7 @@ const formatBaseModel = (model: BaseModel): string =>
46
46
  ["Provider", model.provider],
47
47
  ["Upstream model", model.upstreamModel],
48
48
  ["Upstream revision", model.upstreamRevision],
49
+ ["Upstream snapshot", model.upstreamUrl],
49
50
  ]);
50
51
 
51
52
  const formatModel = (model: Model): string =>
@@ -62,7 +63,11 @@ const formatModel = (model: Model): string =>
62
63
 
63
64
  export const parseBaseModelId = (value: string): BaseModelId => {
64
65
  const modelId = BASE_MODEL_IDS.find((candidate) => candidate === value);
65
- assert(modelId, `Unknown base model ${value}.`);
66
+ assertCli(
67
+ modelId,
68
+ `Unknown base model ${value}.`,
69
+ { hint: { description: "Run toloka base-model list to see the catalog." } },
70
+ );
66
71
  return modelId;
67
72
  };
68
73
 
@@ -76,7 +81,11 @@ const resolveModel = async (
76
81
  for await (const model of client.models.listAll({ projectId, limit: 100 })) {
77
82
  if (model.name === nameOrId) matches.push(model);
78
83
  }
79
- assert(matches.length === 1, `Model ${nameOrId} was not found or is ambiguous.`);
84
+ assertCli(
85
+ matches.length === 1,
86
+ `Model ${nameOrId} was not found or is ambiguous.`,
87
+ { hint: { description: "Run toloka model list and pass the model id (mdl.…)." } },
88
+ );
80
89
  return matches[0]!;
81
90
  };
82
91
 
@@ -87,8 +96,16 @@ const resolveModelVersion = async (
87
96
  ): Promise<ModelVersion> => {
88
97
  if (nameOrId.startsWith("mdv.")) return client.models.getVersion({ modelVersionId: nameOrId });
89
98
  const separator = nameOrId.lastIndexOf("@");
90
- assert(separator > 0, `Model Version ${nameOrId} was not found. Use mdv.<id> or Model@name.`);
91
- assert(projectId, "Select a project to resolve Model Version names.");
99
+ assertCli(
100
+ separator > 0,
101
+ `Model Version ${nameOrId} was not found. Use mdv.<id> or Model@name.`,
102
+ { hint: { disabled: true } },
103
+ );
104
+ assertCli(
105
+ projectId,
106
+ "Select a project to resolve Model Version names.",
107
+ { hint: { description: "Select a project with toloka project add or toloka project switch, or pass the version id (mdv.…) directly." } },
108
+ );
92
109
  const model = await resolveModel(client, projectId, nameOrId.slice(0, separator));
93
110
  const versionName = nameOrId.slice(separator + 1);
94
111
  const matches: ModelVersion[] = [];
@@ -99,7 +116,11 @@ const resolveModelVersion = async (
99
116
  })) {
100
117
  if (version.name === versionName) matches.push(version);
101
118
  }
102
- assert(matches.length === 1, `Model Version ${nameOrId} was not found or is ambiguous.`);
119
+ assertCli(
120
+ matches.length === 1,
121
+ `Model Version ${nameOrId} was not found or is ambiguous.`,
122
+ { hint: { description: "Run toloka model versions <model> and pass the version id (mdv.…)." } },
123
+ );
103
124
  return matches[0]!;
104
125
  };
105
126
 
@@ -116,12 +137,14 @@ export const listBaseModels = async (
116
137
  models.length === 0
117
138
  ? "No base models found."
118
139
  : formatTable([
119
- ["ID", "NAME", "PROVIDER", "UPSTREAM MODEL"],
140
+ ["ID", "NAME", "PROVIDER", "UPSTREAM MODEL", "REVISION", "UPSTREAM SNAPSHOT"],
120
141
  ...models.map((model) => [
121
142
  model.id,
122
143
  model.displayName,
123
144
  model.provider,
124
145
  model.upstreamModel,
146
+ model.upstreamRevision.slice(0, 12),
147
+ model.upstreamUrl,
125
148
  ]),
126
149
  ]),
127
150
  );
@@ -238,25 +261,31 @@ export const downloadModelVersion = async (
238
261
  for (const file of descriptor.files) {
239
262
  const target = resolve(root, file.path);
240
263
  const relativeTarget = relative(root, target);
241
- assert(
264
+ assertCli(
242
265
  relativeTarget !== "" &&
243
266
  relativeTarget !== ".." &&
244
267
  !relativeTarget.startsWith(`..${sep}`) &&
245
268
  !isAbsolute(relativeTarget),
246
269
  `Invalid Model Version file path: ${file.path}`,
270
+ { hint: REPORT_HINT },
247
271
  );
248
272
  let parent = root;
249
273
  for (const segment of file.path.split("/").slice(0, -1)) {
250
274
  parent = resolve(parent, segment);
251
275
  await mkdir(parent, { recursive: true });
252
276
  const realParent = await realpath(parent);
253
- assert(
277
+ assertCli(
254
278
  realParent === realRoot || realParent.startsWith(`${realRoot}${sep}`),
255
279
  `Invalid Model Version file path: ${file.path}`,
280
+ { hint: REPORT_HINT },
256
281
  );
257
282
  }
258
283
  const response = await fetch(file.url, { redirect: "error" });
259
- assert(response.ok && response.body, `Failed to download Model Version file ${file.path}.`);
284
+ assertCli(
285
+ response.ok && response.body,
286
+ `Failed to download Model Version file ${file.path}.`,
287
+ { hint: RETRY_OR_REPORT_HINT },
288
+ );
260
289
  const hash = createHash("sha256");
261
290
  let byteSize = 0;
262
291
  const verify = new Transform({
@@ -274,13 +303,15 @@ export const downloadModelVersion = async (
274
303
  0o600,
275
304
  );
276
305
  await pipeline(Readable.from(response.body), verify, handle.createWriteStream());
277
- assert(
306
+ assertCli(
278
307
  byteSize === file.byteSize,
279
308
  `Downloaded Model Version file ${file.path} has an unexpected size.`,
309
+ { hint: { description: "Delete the partial download and retry; if it keeps failing, run toloka report." } },
280
310
  );
281
- assert(
311
+ assertCli(
282
312
  hash.digest("hex") === file.sha256,
283
313
  `Downloaded Model Version file ${file.path} has an invalid hash.`,
314
+ { hint: { description: "Delete the partial download and retry; if it keeps failing, run toloka report." } },
284
315
  );
285
316
  await rename(temporary, target);
286
317
  } catch (error) {