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

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 (79) hide show
  1. package/README.md +10 -4
  2. package/dist/cli/toloka.js +159 -151
  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 +4 -3
  8. package/docs/cli.md +29 -10
  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/checkReport.ts +2 -1
  16. package/openapi/gen/models/checkReportAnyOfTwoseven.ts +5 -0
  17. package/openapi/gen/models/checkReportAnyOfTwosevenResultsItem.ts +6 -0
  18. package/openapi/gen/models/checkReportAnyOfTwosevenResultsItemVerdict.ts +2 -0
  19. package/openapi/gen/models/currentUser.ts +1 -0
  20. package/openapi/gen/models/deliveryOrder.ts +9 -0
  21. package/openapi/gen/models/deliveryOrderClientName.ts +2 -0
  22. package/openapi/gen/models/getTrainingRunMetricHistoryParams.ts +4 -0
  23. package/openapi/gen/models/index.ts +19 -0
  24. package/openapi/gen/models/listDeliveryOrders200.ts +5 -0
  25. package/openapi/gen/models/trainingRunMetricHistory.ts +9 -0
  26. package/openapi/gen/models/trainingRunMetricHistoryMetric.ts +2 -0
  27. package/openapi/gen/models/trainingRunMetricHistoryPointsItem.ts +9 -0
  28. package/openapi/gen/models/trainingRunMetricHistoryPointsItemNonFinite.ts +2 -0
  29. package/openapi/gen/models/trainingRunMetricHistoryPointsItemValue.ts +2 -0
  30. package/openapi/gen/models/trainingRunMetrics.ts +9 -0
  31. package/openapi/gen/models/trainingRunMetricsMetricsItem.ts +8 -0
  32. package/openapi/gen/models/trainingRunMetricsMetricsItemLatest.ts +9 -0
  33. package/openapi/gen/models/trainingRunMetricsMetricsItemLatestNonFinite.ts +2 -0
  34. package/openapi/gen/models/trainingRunMetricsMetricsItemLatestValue.ts +2 -0
  35. package/openapi/gen/models/trainingRunMetricsMetricsItemMetric.ts +2 -0
  36. package/openapi/gen/models/trainingRunMetricsUnavailableReason.ts +2 -0
  37. package/package.json +2 -2
  38. package/skills/catalog.json +55 -7
  39. package/skills/toloka-build/SKILL.md +10 -7
  40. package/skills/toloka-build/references/node-semantics.md +6 -4
  41. package/skills/toloka-build/references/workflow-and-cli.md +21 -13
  42. package/skills/toloka-fine-tuning/SKILL.md +72 -0
  43. package/skills/toloka-get-started/SKILL.md +55 -0
  44. package/skills/toloka-gisting/SKILL.md +76 -0
  45. package/skills/toloka-report-issue/references/report-template.md +1 -1
  46. package/src/authoring/index.ts +14 -0
  47. package/src/checks/task-ui-typecheck.ts +12 -10
  48. package/src/cli/analytics.ts +52 -6
  49. package/src/cli/auth.ts +69 -16
  50. package/src/cli/auto-update.ts +24 -33
  51. package/src/cli/check.ts +12 -10
  52. package/src/cli/commands/auth.ts +89 -0
  53. package/src/cli/commands/config.ts +28 -0
  54. package/src/cli/commands/dataset.ts +75 -0
  55. package/src/cli/commands/delivery-order.ts +18 -0
  56. package/src/cli/commands/models.ts +122 -0
  57. package/src/cli/commands/pipeline.ts +132 -0
  58. package/src/cli/commands/project.ts +175 -0
  59. package/src/cli/commands/report.ts +39 -0
  60. package/src/cli/commands/secret.ts +39 -0
  61. package/src/cli/commands/skill.ts +36 -0
  62. package/src/cli/commands/source.ts +204 -0
  63. package/src/cli/commands/status.ts +18 -0
  64. package/src/cli/commands/training.ts +228 -0
  65. package/src/cli/commands/version.ts +14 -0
  66. package/src/cli/config.ts +5 -6
  67. package/src/cli/context.ts +213 -0
  68. package/src/cli/delivery-orders.ts +30 -0
  69. package/src/cli/environment.ts +26 -0
  70. package/src/cli/init.ts +1 -4
  71. package/src/cli/program.ts +168 -1344
  72. package/src/cli/project-context.ts +9 -11
  73. package/src/cli/sdk-version.ts +38 -0
  74. package/src/cli/skill.ts +6 -2
  75. package/src/cli/terminal-io.ts +103 -0
  76. package/src/cli/trainings.ts +118 -27
  77. package/src/cli/version.ts +1 -1
  78. package/src/client.ts +18 -0
  79. package/src/importer.ts +32 -2
@@ -29,6 +29,7 @@ export type ProjectCreateApi = {
29
29
  readonly create: (input: {
30
30
  readonly teamId: string;
31
31
  readonly name?: string;
32
+ readonly deliveryOrderId?: string;
32
33
  }) => Promise<ProjectRecord>;
33
34
  readonly listAll: (params?: object) => AsyncIterable<ProjectRecord>;
34
35
  };
@@ -41,16 +42,11 @@ export const NO_PROJECT_SELECTED_MESSAGE =
41
42
  "No project selected. Run toloka project create for a new project, toloka project add for an existing project, or cd into a checkout.";
42
43
 
43
44
  const NETWORK_MISMATCH_MESSAGE =
44
- "project add writes the machine registry; unset --api-key / --base-url / POTATO_API_KEY / POTATO_BASE_URL, or run login against that platform first.";
45
+ "project add writes the machine registry; unset --api-key / --base-url / TOLOKA_API_KEY / TOLOKA_BASE_URL, or run login against that platform first.";
45
46
 
46
47
  const ALIAS_PATTERN = /^[^/\s]+$/u;
47
48
 
48
- export type ProjectContextSource =
49
- | "flag"
50
- | "toloka-project-id"
51
- | "potato-project-id"
52
- | "toloka.json"
53
- | "registry";
49
+ export type ProjectContextSource = "flag" | "toloka-project-id" | "toloka.json" | "registry";
54
50
 
55
51
  export type ProjectContext = {
56
52
  readonly id: string;
@@ -93,7 +89,7 @@ const effectiveNetworkBaseUrl = (
93
89
  stored: StoredCredentials | undefined,
94
90
  ): string | undefined =>
95
91
  firstNonEmpty(flags?.baseUrl) ??
96
- firstNonEmpty(environment?.POTATO_BASE_URL) ??
92
+ firstNonEmpty(environment?.TOLOKA_BASE_URL) ??
97
93
  resolveInternalBaseUrl(environment ?? {}) ??
98
94
  firstNonEmpty(stored?.baseUrl);
99
95
 
@@ -144,7 +140,6 @@ export function resolveProjectContext(
144
140
  }> = [
145
141
  { id: firstNonEmpty(flags.projectId), source: "flag" },
146
142
  { id: firstNonEmpty(environment.TOLOKA_PROJECT_ID), source: "toloka-project-id" },
147
- { id: firstNonEmpty(environment.POTATO_PROJECT_ID), source: "potato-project-id" },
148
143
  { id: firstNonEmpty(options.state?.projectId), source: "toloka.json" },
149
144
  {
150
145
  id:
@@ -183,10 +178,10 @@ const assertStoredNetworkMatches = (
183
178
  environment: TolokaEnvironment,
184
179
  stored: StoredCredentials,
185
180
  ) => {
186
- const flagOrEnvKey = firstNonEmpty(flags.apiKey) ?? firstNonEmpty(environment.POTATO_API_KEY);
181
+ const flagOrEnvKey = firstNonEmpty(flags.apiKey) ?? firstNonEmpty(environment.TOLOKA_API_KEY);
187
182
  const flagOrEnvBase =
188
183
  firstNonEmpty(flags.baseUrl) ??
189
- firstNonEmpty(environment.POTATO_BASE_URL) ??
184
+ firstNonEmpty(environment.TOLOKA_BASE_URL) ??
190
185
  resolveInternalBaseUrl(environment);
191
186
  assert(flagOrEnvKey === undefined || flagOrEnvKey === stored.apiKey, NETWORK_MISMATCH_MESSAGE);
192
187
  assert(
@@ -464,6 +459,7 @@ export async function createProject(options: {
464
459
  readonly alias?: string;
465
460
  readonly name?: string;
466
461
  readonly teamId?: string;
462
+ readonly deliveryOrderId?: string;
467
463
  readonly sourceRoot?: string;
468
464
  readonly afterPreflight?: () => Promise<void>;
469
465
  }): Promise<{ readonly output: string; readonly projectId: string; readonly alias: string }> {
@@ -489,9 +485,11 @@ export async function createProject(options: {
489
485
  await options.afterPreflight?.();
490
486
 
491
487
  const teamId = await resolveTeamId(options.client, options.teamId);
488
+ const deliveryOrderId = firstNonEmpty(options.deliveryOrderId);
492
489
  const created = await options.client.projects.create({
493
490
  teamId,
494
491
  name: firstNonEmpty(options.name) ?? alias,
492
+ ...(deliveryOrderId === undefined ? {} : { deliveryOrderId }),
495
493
  });
496
494
 
497
495
  try {
@@ -0,0 +1,38 @@
1
+ import packageMetadata from "../../package.json" with { type: "json" };
2
+
3
+ export const SDK_PACKAGE_NAME = packageMetadata.name;
4
+ export const SDK_VERSION = packageMetadata.version;
5
+
6
+ export type ReleaseChannel = "latest" | "preview";
7
+
8
+ export const parseSdkVersion = (version: string): readonly number[] | undefined => {
9
+ const match = /^(\d+)\.(\d+)\.(\d+)(?:-preview\.(\d+))?$/u.exec(version);
10
+ if (match === null) return undefined;
11
+ const [, major, minor, patch, preview] = match;
12
+ return [
13
+ Number(major),
14
+ Number(minor),
15
+ Number(patch),
16
+ preview === undefined ? Number.POSITIVE_INFINITY : Number(preview),
17
+ ];
18
+ };
19
+
20
+ export const releaseChannelFor = (version: string): ReleaseChannel | undefined => {
21
+ const parsed = parseSdkVersion(version);
22
+ if (parsed === undefined) return undefined;
23
+ return parsed[3] === Number.POSITIVE_INFINITY ? "latest" : "preview";
24
+ };
25
+
26
+ export const compareSdkVersions = (left: string, right: string): number | undefined => {
27
+ const parsedLeft = parseSdkVersion(left);
28
+ const parsedRight = parseSdkVersion(right);
29
+ if (parsedLeft === undefined || parsedRight === undefined) return undefined;
30
+ const length = Math.max(parsedLeft.length, parsedRight.length);
31
+ for (let index = 0; index < length; index += 1) {
32
+ const leftPart = parsedLeft[index] ?? 0;
33
+ const rightPart = parsedRight[index] ?? 0;
34
+ if (leftPart < rightPart) return -1;
35
+ if (leftPart > rightPart) return 1;
36
+ }
37
+ return 0;
38
+ };
package/src/cli/skill.ts CHANGED
@@ -4,6 +4,7 @@ import { copyFile, mkdir, mkdtemp, readFile, realpath, rename, rm, stat } from "
4
4
  import { dirname, join } from "node:path";
5
5
  import { assertSafeDestination, entryStats, hasErrorCode } from "./file-system";
6
6
  import { formatCliError } from "./format-error";
7
+ import { releaseChannelFor, SDK_PACKAGE_NAME, SDK_VERSION } from "./sdk-version";
7
8
 
8
9
  export const SKILL_CATALOG_MANIFEST = "catalog.json";
9
10
  export const SKILL_INSTALL_ROOT = ".agents/skills";
@@ -15,6 +16,9 @@ const MAX_MANIFEST_BYTES = 1024 * 1024;
15
16
  const MAX_SKILL_ID_LENGTH = 64;
16
17
  const SKILL_ID_PATTERN = /^[a-z0-9]+(?:-[a-z0-9]+)*$/u;
17
18
  const DIGEST_PATTERN = /^[a-f0-9]{64}$/u;
19
+ const UPGRADE_HINT = `bun add --global ${SDK_PACKAGE_NAME}@${
20
+ releaseChannelFor(SDK_VERSION) ?? "latest"
21
+ }`;
18
22
 
19
23
  export type SkillDescriptor = {
20
24
  readonly id: string;
@@ -269,7 +273,7 @@ export async function installSkill({
269
273
  const skill = catalog.skills.find((candidate) => candidate.id === skillId);
270
274
  assert(
271
275
  skill,
272
- `Skill ${JSON.stringify(skillId)} is not in the catalog. Run \`toloka skill list\` to see the available skills.`,
276
+ `Skill ${JSON.stringify(skillId)} is not in the catalog bundled with ${SDK_PACKAGE_NAME} ${SDK_VERSION}. Run \`toloka skill list\` to see the available skills; a newer release may ship it (${UPGRADE_HINT}).`,
273
277
  );
274
278
  const relativeDestination = `${SKILL_INSTALL_ROOT}/${skill.id}`;
275
279
  await assertSafeDestination(sourceRoot, relativeDestination);
@@ -348,7 +352,7 @@ export async function skillSearchCommand({
348
352
  return;
349
353
  }
350
354
  io.stdout(
351
- `No skills match ${JSON.stringify(query)}. Run \`toloka skill list\` to see the catalog.`,
355
+ `No skills match ${JSON.stringify(query)} in the catalog bundled with ${SDK_PACKAGE_NAME} ${SDK_VERSION}. Run \`toloka skill list\` to see it; a newer release may add more (${UPGRADE_HINT}).`,
352
356
  );
353
357
  }
354
358
 
@@ -0,0 +1,103 @@
1
+ import { createInterface } from "node:readline/promises";
2
+ import type { AuthPrompt } from "./auth";
3
+ import type { CliIo } from "./context";
4
+ import { pickPipeline } from "./runs";
5
+
6
+ const readStandardInput = async (): Promise<string> => {
7
+ process.stdin.setEncoding("utf8");
8
+ const chunks: string[] = [];
9
+ for await (const chunk of process.stdin) chunks.push(String(chunk));
10
+ return chunks.join("");
11
+ };
12
+
13
+ const readMaskedLine = async (question: string): Promise<string> => {
14
+ process.stdout.write(question);
15
+ const wasRaw = process.stdin.isRaw;
16
+ process.stdin.setRawMode(true);
17
+ process.stdin.resume();
18
+ process.stdin.setEncoding("utf8");
19
+
20
+ const { promise, resolve, reject } = Promise.withResolvers<string>();
21
+ let value = "";
22
+
23
+ const restore = () => {
24
+ process.stdin.off("data", onData);
25
+ process.stdin.off("end", onEnd);
26
+ process.stdin.off("error", onError);
27
+ process.stdin.setRawMode(wasRaw);
28
+ process.stdin.pause();
29
+ };
30
+
31
+ const finish = () => {
32
+ restore();
33
+ process.stdout.write("\n");
34
+ };
35
+
36
+ const onEnd = () => {
37
+ finish();
38
+ reject(new Error("Standard input closed before an API key was entered."));
39
+ };
40
+
41
+ const onError = (error: Error) => {
42
+ finish();
43
+ reject(error);
44
+ };
45
+
46
+ const onData = (chunk: string) => {
47
+ for (const character of chunk) {
48
+ if (character === "\r" || character === "\n") {
49
+ finish();
50
+ resolve(value);
51
+ return;
52
+ }
53
+ if (character === "\u0003" || character === "\u0004") {
54
+ finish();
55
+ reject(
56
+ new Error(
57
+ character === "\u0003"
58
+ ? "Canceled."
59
+ : "Standard input closed before an API key was entered.",
60
+ ),
61
+ );
62
+ return;
63
+ }
64
+ if (character === "\u007f" || character === "\b") {
65
+ if (value.length > 0) {
66
+ value = value.slice(0, -1);
67
+ process.stdout.write("\b \b");
68
+ }
69
+ continue;
70
+ }
71
+ value += character;
72
+ process.stdout.write("*");
73
+ }
74
+ };
75
+
76
+ process.stdin.on("data", onData);
77
+ process.stdin.once("end", onEnd);
78
+ process.stdin.once("error", onError);
79
+
80
+ try {
81
+ return await promise;
82
+ } finally {
83
+ restore();
84
+ }
85
+ };
86
+
87
+ const promptFromTerminal: AuthPrompt = async (question, options = {}) => {
88
+ if (options.mask === true) return readMaskedLine(question);
89
+
90
+ const input = createInterface({ input: process.stdin, output: process.stdout, terminal: true });
91
+ try {
92
+ return await input.question(question);
93
+ } finally {
94
+ input.close();
95
+ }
96
+ };
97
+
98
+ export const terminalIo = (): CliIo => ({
99
+ stdout: (text) => console.log(text),
100
+ stderr: (text) => console.error(text),
101
+ readStdin: readStandardInput,
102
+ ...(process.stdin.isTTY ? { prompt: promptFromTerminal, select: pickPipeline } : {}),
103
+ });
@@ -5,7 +5,14 @@ import { randomUUID } from "node:crypto";
5
5
 
6
6
  import { assert } from "es-toolkit";
7
7
 
8
- import type { PotatoClient, Training, TrainingCostEstimate, TrainingRun } from "../client";
8
+ import type {
9
+ PotatoClient,
10
+ Training,
11
+ TrainingCostEstimate,
12
+ TrainingRun,
13
+ TrainingRunMetricHistory,
14
+ TrainingRunMetrics,
15
+ } from "../client";
9
16
  import type { BaseModelId, TrainingConfigurationInput } from "../../openapi/gen/models";
10
17
 
11
18
  type TrainingMethod = TrainingConfigurationInput["method"];
@@ -33,6 +40,7 @@ export type TrainingDraftOptions = {
33
40
  type TrainingApi = Pick<PotatoClient, "trainings">;
34
41
  type TrainingDraftApi = Pick<PotatoClient, "trainings" | "files" | "datasets">;
35
42
  type TrainingCostApi = Pick<PotatoClient, "trainings" | "waitFor">;
43
+ type TrainingRunApi = Pick<PotatoClient, "trainings" | "waitFor">;
36
44
  const GISTING_PROMPT_MAX_BYTES = 256 * 1024;
37
45
  type TrainingIo = { readonly stdout: (text: string) => void };
38
46
  type TableRow = readonly string[];
@@ -446,7 +454,10 @@ export const createTraining = async (
446
454
  io: TrainingIo,
447
455
  ): Promise<void> => {
448
456
  assert(input.datasetId, "--dataset is required.");
449
- assert(!input.validationFilter || input.validationDatasetId, "--validation-filter requires --validation-dataset.");
457
+ assert(
458
+ !input.validationFilter || input.validationDatasetId,
459
+ "--validation-filter requires --validation-dataset.",
460
+ );
450
461
 
451
462
  const configuration = await buildTrainingConfiguration({
452
463
  client,
@@ -458,12 +469,7 @@ export const createTraining = async (
458
469
  name: input.name,
459
470
  configuration,
460
471
  });
461
- writeOutput(
462
- io,
463
- input.json,
464
- training,
465
- `Created training ${training.id}`,
466
- );
472
+ writeOutput(io, input.json, training, `Created training ${training.id}`);
467
473
  };
468
474
 
469
475
  export const updateTraining = async (
@@ -485,19 +491,13 @@ export const updateTraining = async (
485
491
  options: input,
486
492
  }),
487
493
  });
488
- writeOutput(
489
- io,
490
- input.json,
491
- updated,
492
- `Updated training ${updated.id}`,
493
- );
494
+ writeOutput(io, input.json, updated, `Updated training ${updated.id}`);
494
495
  };
495
496
 
496
- export const estimateTrainingCost = async (
497
+ const prepareTrainingCostEstimate = async (
497
498
  client: TrainingCostApi,
498
- input: { projectId: string; nameOrId: string; idempotencyKey?: string; json: boolean },
499
- io: TrainingIo,
500
- ): Promise<void> => {
499
+ input: { nameOrId: string; projectId: string; idempotencyKey?: string },
500
+ ): Promise<{ training: Training; estimate: TrainingCostEstimate }> => {
501
501
  const training = await resolveTraining(client, input.projectId, input.nameOrId);
502
502
  const inputSet = await client.trainings.prepareCostEstimateInputs({
503
503
  trainingId: training.id,
@@ -511,33 +511,53 @@ export const estimateTrainingCost = async (
511
511
  exportIds.map((exportId) => client.waitFor({ type: "datasetExport", exportId })),
512
512
  );
513
513
 
514
- const estimate = await client.trainings.estimateCost({
515
- trainingId: training.id,
516
- inputSetId: inputSet.id,
517
- });
514
+ return {
515
+ training,
516
+ estimate: await client.trainings.estimateCost({
517
+ trainingId: training.id,
518
+ inputSetId: inputSet.id,
519
+ }),
520
+ };
521
+ };
522
+
523
+ export const estimateTrainingCost = async (
524
+ client: TrainingCostApi,
525
+ input: { projectId: string; nameOrId: string; idempotencyKey?: string; json: boolean },
526
+ io: TrainingIo,
527
+ ): Promise<void> => {
528
+ const { estimate } = await prepareTrainingCostEstimate(client, input);
518
529
  writeOutput(io, input.json, estimate, formatCostEstimate(estimate));
519
530
  };
520
531
 
521
532
  export const createTrainingRun = async (
522
- client: TrainingApi,
533
+ client: TrainingRunApi,
523
534
  input: {
524
535
  projectId: string;
525
536
  nameOrId: string;
526
- estimateFile: string;
527
- versionName: string;
537
+ estimateFile?: string;
538
+ versionName?: string;
528
539
  idempotencyKey?: string;
529
540
  json: boolean;
530
541
  },
531
542
  io: TrainingIo,
532
543
  ): Promise<void> => {
533
544
  const training = await resolveTraining(client, input.projectId, input.nameOrId);
534
- const estimate = parseReadyEstimateFile(JSON.parse(await readFile(input.estimateFile, "utf8")));
545
+ const estimate = input.estimateFile
546
+ ? parseReadyEstimateFile(JSON.parse(await readFile(input.estimateFile, "utf8")))
547
+ : await (async () => {
548
+ const prepared = await prepareTrainingCostEstimate(client, {
549
+ projectId: input.projectId,
550
+ nameOrId: input.nameOrId,
551
+ });
552
+ assert(prepared.estimate.status === "ready", formatCostEstimate(prepared.estimate));
553
+ return parseReadyEstimateFile(prepared.estimate);
554
+ })();
535
555
  assert(estimate.trainingId === training.id, "The estimate file belongs to another Training.");
536
556
  const run = await client.trainings.createRun({
537
557
  trainingId: training.id,
538
558
  inputSetId: estimate.inputSetId,
539
559
  estimateFingerprint: estimate.fingerprint,
540
- versionName: input.versionName,
560
+ versionName: input.versionName ?? training.name,
541
561
  idempotencyKey: input.idempotencyKey ?? randomUUID(),
542
562
  });
543
563
  writeOutput(io, input.json, run, formatTrainingRun(run));
@@ -625,3 +645,74 @@ export const readTrainingRunLogs = async (
625
645
  });
626
646
  }
627
647
  };
648
+
649
+ const formatMetricValue = (point: {
650
+ value: number | null;
651
+ nonFinite: "nan" | "positive_infinity" | "negative_infinity" | null;
652
+ }): string => {
653
+ if (point.nonFinite === "nan") return "NaN";
654
+ if (point.nonFinite === "positive_infinity") return "Infinity";
655
+ if (point.nonFinite === "negative_infinity") return "-Infinity";
656
+ return point.value === null ? "-" : String(point.value);
657
+ };
658
+
659
+ 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.",
662
+ };
663
+
664
+ const formatTrainingRunMetrics = (metrics: TrainingRunMetrics): string => {
665
+ if (!metrics.available) {
666
+ return metrics.unavailableReason === null
667
+ ? "No metrics are available for this Training Run."
668
+ : METRICS_UNAVAILABLE[metrics.unavailableReason];
669
+ }
670
+ if (metrics.metrics.length === 0) return "No metrics have been reported yet.";
671
+
672
+ const rows = metrics.metrics.map((entry) => [
673
+ entry.key,
674
+ entry.metric ?? "-",
675
+ formatMetricValue(entry.latest),
676
+ String(entry.latest.step),
677
+ ]);
678
+ const table = formatTable([["Metric", "Meaning", "Latest", "Step"], ...rows]);
679
+ return metrics.truncated ? `${table}\n\nMore metrics exist than the server returns.` : table;
680
+ };
681
+
682
+ const formatTrainingRunMetricHistory = (history: TrainingRunMetricHistory): string => {
683
+ const header = formatTable([
684
+ ["Metric", history.key],
685
+ ["Meaning", history.metric ?? "-"],
686
+ ["Points", String(history.points.length)],
687
+ ]);
688
+ const points = formatTable([
689
+ ["Step", "Value"],
690
+ ...history.points.map((point) => [String(point.step), formatMetricValue(point)]),
691
+ ]);
692
+ const note = history.truncated
693
+ ? "\n\nNot every recorded point is shown: the series is thinned to fit --resolution, and a very long run keeps only its most recent points."
694
+ : "";
695
+ return `${header}\n\n${points}${note}`;
696
+ };
697
+
698
+ export const readTrainingRunMetrics = async (
699
+ client: TrainingApi,
700
+ input: { trainingRunId: string; json: boolean },
701
+ io: TrainingIo,
702
+ ): Promise<void> => {
703
+ const metrics = await client.trainings.getRunMetrics({ trainingRunId: input.trainingRunId });
704
+ writeOutput(io, input.json, metrics, formatTrainingRunMetrics(metrics));
705
+ };
706
+
707
+ export const readTrainingRunMetricHistory = async (
708
+ client: TrainingApi,
709
+ input: { trainingRunId: string; metricKey: string; resolution?: number; json: boolean },
710
+ io: TrainingIo,
711
+ ): Promise<void> => {
712
+ const history = await client.trainings.getRunMetricHistory({
713
+ trainingRunId: input.trainingRunId,
714
+ metricKey: input.metricKey,
715
+ resolution: input.resolution,
716
+ });
717
+ writeOutput(io, input.json, history, formatTrainingRunMetricHistory(history));
718
+ };
@@ -3,7 +3,7 @@ import { homedir } from "node:os";
3
3
  import { dirname, join, resolve, sep } from "node:path";
4
4
 
5
5
  import { assert } from "es-toolkit";
6
- import { SDK_VERSION } from "./init";
6
+ import { SDK_VERSION } from "./sdk-version";
7
7
 
8
8
  export type CliInstallChannel = "global-registry" | "linked" | "project-local";
9
9
 
package/src/client.ts CHANGED
@@ -25,6 +25,7 @@ import type {
25
25
  ImportPipelineBody,
26
26
  InferredField as GeneratedInferredField,
27
27
  GetTrainingRunLogsParams,
28
+ GetTrainingRunMetricHistoryParams,
28
29
  ListDatasetFieldsParams,
29
30
  ListModelVersionsParams,
30
31
  ListDatasetItemsParams,
@@ -45,6 +46,8 @@ import type {
45
46
  TrainingCostEstimate as GeneratedTrainingCostEstimate,
46
47
  TrainingCostEstimateInputSet as GeneratedTrainingCostEstimateInputSet,
47
48
  TrainingRun as GeneratedTrainingRun,
49
+ TrainingRunMetricHistory as GeneratedTrainingRunMetricHistory,
50
+ TrainingRunMetrics as GeneratedTrainingRunMetrics,
48
51
  TrainingRunLogs as GeneratedTrainingRunLogs,
49
52
  ReplaceTrainingConfigurationBody,
50
53
  UpdateTrainingMetadataBody,
@@ -164,6 +167,8 @@ export type TrainingCostEstimateInputSet = BrandedResource<
164
167
  export type TrainingCostEstimate = BrandResourceUnion<GeneratedTrainingCostEstimate>;
165
168
  export type TrainingRun = BrandedResource<GeneratedTrainingRun, TrainingRunId>;
166
169
  export type TrainingRunLogs = GeneratedTrainingRunLogs;
170
+ export type TrainingRunMetrics = GeneratedTrainingRunMetrics;
171
+ export type TrainingRunMetricHistory = GeneratedTrainingRunMetricHistory;
167
172
  export type BaseModel = GeneratedBaseModel;
168
173
  export type BaseModelId = GeneratedBaseModelId;
169
174
  export type Pipeline = BrandedResource<GeneratedPipeline, PipelineId>;
@@ -482,6 +487,14 @@ const createClientImplementation = (config: ClientConfig) => {
482
487
  ...params
483
488
  }: { trainingRunId: TrainingRunId } & GetTrainingRunLogsParams) =>
484
489
  wire.getTrainingRunLogs(trainingRunId, params, requestOptions),
490
+ getRunMetrics: ({ trainingRunId }: { trainingRunId: TrainingRunId }) =>
491
+ wire.getTrainingRunMetrics(trainingRunId, requestOptions),
492
+ getRunMetricHistory: ({
493
+ trainingRunId,
494
+ metricKey,
495
+ ...params
496
+ }: { trainingRunId: TrainingRunId; metricKey: string } & GetTrainingRunMetricHistoryParams) =>
497
+ wire.getTrainingRunMetricHistory(trainingRunId, metricKey, params, requestOptions),
485
498
  };
486
499
 
487
500
  const datasets = {
@@ -624,6 +637,10 @@ const createClientImplementation = (config: ClientConfig) => {
624
637
  wire.getPipelineCheck(checkRunId, requestOptions),
625
638
  };
626
639
 
640
+ const deliveryOrders = {
641
+ list: async () => (await wire.listDeliveryOrders(requestOptions)).deliveryOrders,
642
+ };
643
+
627
644
  return {
628
645
  me: () => wire.getCurrentUser(requestOptions),
629
646
  projects,
@@ -636,6 +653,7 @@ const createClientImplementation = (config: ClientConfig) => {
636
653
  secrets,
637
654
  files,
638
655
  checks,
656
+ deliveryOrders,
639
657
  waitFor,
640
658
  };
641
659
  };
package/src/importer.ts CHANGED
@@ -263,6 +263,32 @@ function transformExam<Exam extends { gradedEntities: Array<{ targetEntity: stri
263
263
  };
264
264
  }
265
265
 
266
+ const generationCheckNames: readonly string[] = [
267
+ "ui",
268
+ "llmqa",
269
+ "qc_lint",
270
+ "ui_types_lint",
271
+ "exam_dataset",
272
+ "synthgen",
273
+ ];
274
+ const quorumCheckNames: readonly string[] = [
275
+ "ui",
276
+ "llmqa",
277
+ "qc_lint",
278
+ "ui_types_lint",
279
+ "exam_dataset",
280
+ ];
281
+
282
+ const normalizeImportedChecks = (
283
+ checks: Record<string, unknown[]> | undefined,
284
+ requiredNames: readonly string[],
285
+ ): Record<string, unknown[]> | undefined => {
286
+ if (checks === undefined) return undefined;
287
+ const normalized = { ...checks };
288
+ for (const name of requiredNames) normalized[name] ??= [];
289
+ return normalized;
290
+ };
291
+
266
292
  function authoringSettings(node: GraphNode, fieldReference: FieldReference): unknown {
267
293
  switch (node.type) {
268
294
  case "root": {
@@ -296,9 +322,11 @@ function authoringSettings(node: GraphNode, fieldReference: FieldReference): unk
296
322
  };
297
323
  }
298
324
  case "generation": {
299
- const { entities, qualityCriteria, exam, ...settings } = node.settings;
325
+ const { entities, qualityCriteria, exam, checks, ...settings } = node.settings;
326
+ const normalizedChecks = normalizeImportedChecks(checks, generationCheckNames);
300
327
  return {
301
328
  ...settings,
329
+ ...(normalizedChecks === undefined ? {} : { checks: normalizedChecks }),
302
330
  entities: {
303
331
  input: entities.input.map((entity) => transformEntity(entity, fieldReference)),
304
332
  output: entities.output.map((entity) => transformEntity(entity, fieldReference)),
@@ -326,9 +354,11 @@ function authoringSettings(node: GraphNode, fieldReference: FieldReference): unk
326
354
  };
327
355
  }
328
356
  case "quorum": {
329
- const { entities, qualityCriteria, exam, ...settings } = node.settings;
357
+ const { entities, qualityCriteria, exam, checks, ...settings } = node.settings;
358
+ const normalizedChecks = normalizeImportedChecks(checks, quorumCheckNames);
330
359
  return {
331
360
  ...settings,
361
+ ...(normalizedChecks === undefined ? {} : { checks: normalizedChecks }),
332
362
  entities: {
333
363
  input: entities.input.map((entity) => transformEntity(entity, fieldReference)),
334
364
  output: entities.output.map((entity) => transformEntity(entity, fieldReference)),