@toloka-platform/sdk 0.1.0-preview.8 → 0.1.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.
- package/README.md +5 -4
- package/dist/cli/toloka.js +138 -136
- 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/authoring.md +9 -4
- package/docs/cli-v2.md +2 -2
- package/docs/cli.md +13 -2
- package/docs/getting-started.md +3 -3
- package/docs/limitations.md +2 -2
- package/package.json +3 -3
- package/skills/catalog.json +4 -4
- package/skills/toloka-build/SKILL.md +1 -1
- package/skills/toloka-build/references/node-semantics.md +2 -2
- package/skills/toloka-build/references/workflow-and-cli.md +2 -1
- package/skills/toloka-report-issue/references/report-template.md +1 -1
- package/src/authoring/index.ts +10 -0
- package/src/checks/task-ui-typecheck.ts +1 -2
- package/src/cli/auto-update.ts +3 -7
- package/src/cli/check.ts +94 -34
- package/src/cli/cli-entry.ts +41 -0
- package/src/cli/cost.ts +7 -1
- package/src/cli/program.ts +28 -2
- package/src/cli/project-context.ts +47 -2
- package/src/cli/project.ts +53 -10
- package/src/cli/skill.ts +4 -5
- package/src/cli/version.ts +68 -0
- package/src/index.ts +2 -0
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
import { assert } from "es-toolkit";
|
|
2
|
-
import { readTolokaState, writeTolokaState } from "../state";
|
|
2
|
+
import { readTolokaState, TOLOKA_STATE_FILENAME, writeTolokaState } from "../state";
|
|
3
3
|
import type { TolokaState } from "../state";
|
|
4
4
|
import type { AuthPrompt } from "./auth";
|
|
5
5
|
import type { TolokaCliFlags, TolokaEnvironment } from "./config";
|
|
@@ -238,7 +238,7 @@ export async function listRemoteProjects(options: {
|
|
|
238
238
|
options.context?.id === project.id && options.context.alias !== undefined
|
|
239
239
|
? `\t${options.context.alias}`
|
|
240
240
|
: "";
|
|
241
|
-
return `${mark}\t${project.id}\t${project.name}${alias}`;
|
|
241
|
+
return `${mark}\t${project.id}\t${project.name}\t${project.teamId}${alias}`;
|
|
242
242
|
});
|
|
243
243
|
if (page.pagination.hasMore) {
|
|
244
244
|
lines.push(
|
|
@@ -250,6 +250,51 @@ export async function listRemoteProjects(options: {
|
|
|
250
250
|
return { output };
|
|
251
251
|
}
|
|
252
252
|
|
|
253
|
+
export type TrackedResourceKind = "pipeline" | "dataset";
|
|
254
|
+
|
|
255
|
+
export async function untrackProjectResource(options: {
|
|
256
|
+
readonly sourceRoot: string;
|
|
257
|
+
readonly type: TrackedResourceKind;
|
|
258
|
+
readonly name: string;
|
|
259
|
+
readonly confirm: boolean;
|
|
260
|
+
readonly io: ProjectIo;
|
|
261
|
+
}): Promise<{ readonly output: string }> {
|
|
262
|
+
const state = await readTolokaState(options.sourceRoot);
|
|
263
|
+
assert(state, `No ${TOLOKA_STATE_FILENAME} found in ${options.sourceRoot}.`);
|
|
264
|
+
const bindings = options.type === "pipeline" ? state.pipelines : state.datasets;
|
|
265
|
+
const binding = bindings[options.name];
|
|
266
|
+
assert(
|
|
267
|
+
binding,
|
|
268
|
+
`${options.type === "pipeline" ? "Pipeline" : "Dataset"} ${options.name} is not tracked.`,
|
|
269
|
+
);
|
|
270
|
+
const id = "pipelineId" in binding ? binding.pipelineId : binding.datasetId;
|
|
271
|
+
const label = `${options.type} ${options.name} (${id})`;
|
|
272
|
+
|
|
273
|
+
if (!options.confirm) {
|
|
274
|
+
assert(
|
|
275
|
+
options.io.prompt,
|
|
276
|
+
`Refusing to untrack ${label} without confirmation. Pass --confirm after reviewing ${TOLOKA_STATE_FILENAME}.`,
|
|
277
|
+
);
|
|
278
|
+
const answer = (
|
|
279
|
+
await options.io.prompt(`Untrack ${label}? The live resource will not be deleted. [y/N] `)
|
|
280
|
+
)
|
|
281
|
+
.trim()
|
|
282
|
+
.toLowerCase();
|
|
283
|
+
assert(answer === "y" || answer === "yes", "Untrack canceled.");
|
|
284
|
+
}
|
|
285
|
+
|
|
286
|
+
const remaining = Object.fromEntries(
|
|
287
|
+
Object.entries(bindings).filter(([candidate]) => candidate !== options.name),
|
|
288
|
+
);
|
|
289
|
+
await writeTolokaState(options.sourceRoot, {
|
|
290
|
+
...state,
|
|
291
|
+
...(options.type === "pipeline" ? { pipelines: remaining } : { datasets: remaining }),
|
|
292
|
+
});
|
|
293
|
+
const output = `Untracked ${label}. The live ${options.type} was not deleted.`;
|
|
294
|
+
options.io.stdout(output);
|
|
295
|
+
return { output };
|
|
296
|
+
}
|
|
297
|
+
|
|
253
298
|
export async function addProject(options: {
|
|
254
299
|
readonly environment: CliEnvironment;
|
|
255
300
|
readonly flags?: TolokaCliFlags;
|
package/src/cli/project.ts
CHANGED
|
@@ -4,6 +4,7 @@ import { dirname, join, resolve } from "node:path";
|
|
|
4
4
|
import type {
|
|
5
5
|
PipelineGraphSnapshot,
|
|
6
6
|
PipelineGraphV1,
|
|
7
|
+
PipelineValidationIssue,
|
|
7
8
|
PipelineValidationReport,
|
|
8
9
|
} from "../../openapi/gen/models";
|
|
9
10
|
import { resolvePipelineDefinition, validatePipelineDefinition } from "../authoring";
|
|
@@ -83,10 +84,13 @@ export type ApplyProjectResult = {
|
|
|
83
84
|
|
|
84
85
|
export type ValidateProjectOptions = PlanProjectOptions;
|
|
85
86
|
|
|
87
|
+
export type PipelineValidationStatus = "valid" | "invalid" | "checks_pending";
|
|
88
|
+
|
|
86
89
|
export type PipelineValidationResult = {
|
|
87
90
|
readonly sourceDirectory: string;
|
|
88
91
|
readonly pipelineId?: string;
|
|
89
92
|
readonly report?: PipelineValidationReport;
|
|
93
|
+
readonly status?: PipelineValidationStatus;
|
|
90
94
|
readonly skipped?: string;
|
|
91
95
|
};
|
|
92
96
|
|
|
@@ -860,10 +864,47 @@ export async function applyProject(
|
|
|
860
864
|
const issueLocation = (issue: { nodeId?: string; checkId?: string }) =>
|
|
861
865
|
[issue.nodeId, issue.checkId].filter((value): value is string => value !== undefined).join("/");
|
|
862
866
|
|
|
867
|
+
const PENDING_CHECK_CODE = "not_ran_node_check";
|
|
868
|
+
|
|
869
|
+
const normalizeValidationIssue = (issue: PipelineValidationIssue): PipelineValidationIssue =>
|
|
870
|
+
issue.code === "empty_node_checks"
|
|
871
|
+
? { ...issue, message: "No stored check cases are configured for this node" }
|
|
872
|
+
: issue;
|
|
873
|
+
|
|
874
|
+
const normalizeValidationReport = (
|
|
875
|
+
report: PipelineValidationReport,
|
|
876
|
+
): { readonly report: PipelineValidationReport; readonly status: PipelineValidationStatus } => {
|
|
877
|
+
const seen = new Set<string>();
|
|
878
|
+
const issues = report.issues.flatMap((rawIssue) => {
|
|
879
|
+
const issue = normalizeValidationIssue(rawIssue);
|
|
880
|
+
const key = [
|
|
881
|
+
issue.code,
|
|
882
|
+
issue.severity,
|
|
883
|
+
issue.nodeId ?? "",
|
|
884
|
+
issue.checkId ?? "",
|
|
885
|
+
issue.message,
|
|
886
|
+
].join("\n");
|
|
887
|
+
if (seen.has(key)) return [];
|
|
888
|
+
seen.add(key);
|
|
889
|
+
return [issue];
|
|
890
|
+
});
|
|
891
|
+
const pending = issues.some((issue) => issue.code === PENDING_CHECK_CODE);
|
|
892
|
+
const blocking = issues.some(
|
|
893
|
+
(issue) => issue.severity === "error" && issue.code !== PENDING_CHECK_CODE,
|
|
894
|
+
);
|
|
895
|
+
const status: PipelineValidationStatus =
|
|
896
|
+
blocking || (!report.valid && !pending) ? "invalid" : pending ? "checks_pending" : "valid";
|
|
897
|
+
return {
|
|
898
|
+
status,
|
|
899
|
+
report: { ...report, valid: status !== "invalid", issues },
|
|
900
|
+
};
|
|
901
|
+
};
|
|
902
|
+
|
|
863
903
|
const formatValidationIssues = (report: PipelineValidationReport) =>
|
|
864
904
|
report.issues.map((issue) => {
|
|
865
905
|
const location = issueLocation(issue);
|
|
866
|
-
|
|
906
|
+
const severity = issue.code === PENDING_CHECK_CODE ? "pending" : issue.severity;
|
|
907
|
+
return ` ${severity} ${issue.code}${location.length === 0 ? "" : ` ${location}`}: ${issue.message}`;
|
|
867
908
|
});
|
|
868
909
|
|
|
869
910
|
export async function validateProject(
|
|
@@ -898,24 +939,26 @@ export async function validateProject(
|
|
|
898
939
|
resolvePipelineDefinition(pipeline.source.definition, { datasetId: dataset.id }),
|
|
899
940
|
pipeline.snapshot.graph,
|
|
900
941
|
);
|
|
901
|
-
const
|
|
902
|
-
|
|
903
|
-
|
|
904
|
-
|
|
942
|
+
const normalized = normalizeValidationReport(
|
|
943
|
+
await client.pipelines.validateGraph({
|
|
944
|
+
pipelineId: pipeline.pipeline.id,
|
|
945
|
+
graph,
|
|
946
|
+
}),
|
|
947
|
+
);
|
|
905
948
|
results.push({
|
|
906
949
|
sourceDirectory: pipeline.source.sourceDirectory,
|
|
907
950
|
pipelineId: pipeline.pipeline.id,
|
|
908
|
-
report,
|
|
951
|
+
report: normalized.report,
|
|
952
|
+
status: normalized.status,
|
|
909
953
|
});
|
|
910
954
|
}
|
|
911
|
-
const valid = results.every((result) => result.
|
|
955
|
+
const valid = results.every((result) => result.status !== "invalid");
|
|
912
956
|
const lines = results.flatMap((result) => {
|
|
913
957
|
if (result.skipped !== undefined)
|
|
914
958
|
return [`- ${result.sourceDirectory}: skipped (${result.skipped})`];
|
|
915
959
|
assert(result.report, `Pipeline ${result.sourceDirectory} is missing a validation report`);
|
|
916
|
-
|
|
917
|
-
|
|
918
|
-
: `- ${result.sourceDirectory}: invalid`;
|
|
960
|
+
assert(result.status, `Pipeline ${result.sourceDirectory} is missing a validation status`);
|
|
961
|
+
const header = `- ${result.sourceDirectory}: ${result.status.replace("_", "-")}`;
|
|
919
962
|
return [header, ...formatValidationIssues(result.report)];
|
|
920
963
|
});
|
|
921
964
|
return {
|
package/src/cli/skill.ts
CHANGED
|
@@ -2,11 +2,9 @@ import { assert } from "es-toolkit";
|
|
|
2
2
|
import { createHash } from "node:crypto";
|
|
3
3
|
import { copyFile, mkdir, mkdtemp, readFile, realpath, rename, rm, stat } from "node:fs/promises";
|
|
4
4
|
import { dirname, join } from "node:path";
|
|
5
|
-
import { fileURLToPath } from "node:url";
|
|
6
5
|
import { assertSafeDestination, entryStats, hasErrorCode } from "./file-system";
|
|
7
6
|
import { formatCliError } from "./format-error";
|
|
8
7
|
|
|
9
|
-
export const BUNDLED_SKILL_CATALOG_ROOT = fileURLToPath(new URL("../../skills", import.meta.url));
|
|
10
8
|
export const SKILL_CATALOG_MANIFEST = "catalog.json";
|
|
11
9
|
export const SKILL_INSTALL_ROOT = ".agents/skills";
|
|
12
10
|
export const SKILL_ENTRY_POINT = "SKILL.md";
|
|
@@ -133,15 +131,16 @@ export async function resolveBundledSkillCatalogRoot(
|
|
|
133
131
|
const runtimeRoot =
|
|
134
132
|
entry === undefined ? undefined : await findSkillCatalogRootAbove(dirname(entry));
|
|
135
133
|
if (runtimeRoot !== undefined) return runtimeRoot;
|
|
134
|
+
const workingDirectoryRoot = await findSkillCatalogRootAbove(process.cwd());
|
|
136
135
|
const searched =
|
|
137
136
|
entry === undefined
|
|
138
137
|
? "process.argv[1] names no runtime entry"
|
|
139
138
|
: `none exists between ${entry} and the filesystem root`;
|
|
140
139
|
assert(
|
|
141
|
-
|
|
142
|
-
`Cannot locate the bundled skill catalog. An installed @toloka-platform/sdk must ship ${SKILL_CATALOG_DIRECTORY}/${SKILL_CATALOG_MANIFEST} in a directory above its runtime entry, but ${searched}, and
|
|
140
|
+
workingDirectoryRoot !== undefined,
|
|
141
|
+
`Cannot locate the bundled skill catalog. An installed @toloka-platform/sdk must ship ${SKILL_CATALOG_DIRECTORY}/${SKILL_CATALOG_MANIFEST} in a directory above its runtime entry, but ${searched}, and no ${SKILL_CATALOG_DIRECTORY}/${SKILL_CATALOG_MANIFEST} exists above ${process.cwd()} either. Reinstall @toloka-platform/sdk.`,
|
|
143
142
|
);
|
|
144
|
-
return
|
|
143
|
+
return workingDirectoryRoot;
|
|
145
144
|
}
|
|
146
145
|
|
|
147
146
|
const loadBundledSkillCatalog = async (): Promise<SkillCatalog> =>
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import { realpathSync } from "node:fs";
|
|
2
|
+
import { homedir } from "node:os";
|
|
3
|
+
import { dirname, join, resolve, sep } from "node:path";
|
|
4
|
+
|
|
5
|
+
import { assert } from "es-toolkit";
|
|
6
|
+
import { SDK_VERSION } from "./init";
|
|
7
|
+
|
|
8
|
+
export type CliInstallChannel = "global-registry" | "linked" | "project-local";
|
|
9
|
+
|
|
10
|
+
type VersionInfoOptions = {
|
|
11
|
+
readonly globalBinDirectory?: string;
|
|
12
|
+
readonly globalPackageDirectory?: string;
|
|
13
|
+
};
|
|
14
|
+
|
|
15
|
+
const GLOBAL_PACKAGE_MARKER = `${sep}install${sep}global${sep}`;
|
|
16
|
+
|
|
17
|
+
const realpathOrSelf = (path: string): string => {
|
|
18
|
+
try {
|
|
19
|
+
return realpathSync(path);
|
|
20
|
+
} catch {
|
|
21
|
+
return path;
|
|
22
|
+
}
|
|
23
|
+
};
|
|
24
|
+
|
|
25
|
+
const classifyInstallChannel = (
|
|
26
|
+
entryPath: string,
|
|
27
|
+
packageRoot: string,
|
|
28
|
+
globalBinDirectory: string,
|
|
29
|
+
globalPackageDirectory: string,
|
|
30
|
+
): CliInstallChannel => {
|
|
31
|
+
const resolvedEntryPath = realpathOrSelf(entryPath);
|
|
32
|
+
if (resolvedEntryPath.includes(GLOBAL_PACKAGE_MARKER)) return "global-registry";
|
|
33
|
+
if (realpathOrSelf(globalPackageDirectory) === packageRoot) return "linked";
|
|
34
|
+
if (dirname(resolve(entryPath)) === resolve(globalBinDirectory)) return "linked";
|
|
35
|
+
return "project-local";
|
|
36
|
+
};
|
|
37
|
+
|
|
38
|
+
export const cliVersionInfo = (entryPath = process.argv[1], options: VersionInfoOptions = {}) => {
|
|
39
|
+
assert(entryPath, "CLI entry path is unavailable");
|
|
40
|
+
const globalBinDirectory =
|
|
41
|
+
options.globalBinDirectory ??
|
|
42
|
+
resolve(process.env.BUN_INSTALL ?? join(homedir(), ".bun"), "bin");
|
|
43
|
+
const globalPackageDirectory =
|
|
44
|
+
options.globalPackageDirectory ??
|
|
45
|
+
resolve(
|
|
46
|
+
globalBinDirectory,
|
|
47
|
+
"..",
|
|
48
|
+
"install",
|
|
49
|
+
"global",
|
|
50
|
+
"node_modules",
|
|
51
|
+
"@toloka-platform",
|
|
52
|
+
"sdk",
|
|
53
|
+
);
|
|
54
|
+
const packageRoot = resolve(dirname(realpathOrSelf(entryPath)), "..");
|
|
55
|
+
const channel = classifyInstallChannel(
|
|
56
|
+
entryPath,
|
|
57
|
+
packageRoot,
|
|
58
|
+
globalBinDirectory,
|
|
59
|
+
globalPackageDirectory,
|
|
60
|
+
);
|
|
61
|
+
|
|
62
|
+
return {
|
|
63
|
+
version: SDK_VERSION,
|
|
64
|
+
commit: process.env.TOLOKA_BUILD_COMMIT ?? null,
|
|
65
|
+
channel,
|
|
66
|
+
...(channel === "linked" ? { path: packageRoot } : {}),
|
|
67
|
+
};
|
|
68
|
+
};
|
package/src/index.ts
CHANGED
|
@@ -69,6 +69,7 @@ export {
|
|
|
69
69
|
connect,
|
|
70
70
|
definePipeline,
|
|
71
71
|
field,
|
|
72
|
+
optional,
|
|
72
73
|
generation,
|
|
73
74
|
isPipelineDefinition,
|
|
74
75
|
match,
|
|
@@ -91,6 +92,7 @@ export type {
|
|
|
91
92
|
InputPort,
|
|
92
93
|
MatchAuthoringSettings,
|
|
93
94
|
OutputPort,
|
|
95
|
+
OptionalFieldBinding,
|
|
94
96
|
PipelineDefinition,
|
|
95
97
|
PipelineDefinitionBindings,
|
|
96
98
|
PipelineDefinitionInput,
|