@toloka-platform/sdk 0.1.0-preview.5 → 0.1.0-preview.7
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 +7 -11
- package/dist/cli/toloka.js +211 -125
- 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 +17 -4
- package/docs/cli-v2.md +3 -3
- package/docs/cli.md +55 -17
- package/docs/datasets.md +8 -9
- package/docs/getting-started.md +7 -12
- package/docs/limitations.md +10 -13
- package/docs/testing-examples.md +248 -0
- package/docs/testing.md +115 -0
- package/docs/troubleshooting.md +0 -11
- package/examples/face-image-collection/README.md +7 -1
- package/examples/face-image-collection/fields.ts +20 -8
- package/examples/face-image-collection/nodes/annotate-faces/node.ts +1 -9
- package/examples/face-image-collection/nodes/order-faces/node.ts +1 -1
- package/examples/face-image-collection/pipeline.browser.integration.spec.ts +78 -0
- package/examples/video-pair-rating/README.md +14 -6
- package/examples/video-pair-rating/nodes/adjudicate/node.ts +1 -3
- package/examples/video-pair-rating/nodes/adjudicate_op/node.ts +1 -3
- package/examples/video-pair-rating/nodes/overall_preference_pick/node.ts +1 -1
- package/examples/video-pair-rating/nodes/rate_pair/node.ts +1 -1
- package/examples/video-pair-rating/pipeline.browser.integration.spec.ts +267 -0
- package/openapi/gen/api.ts +819 -898
- 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/createDatasetExportBody.ts +3 -3
- package/openapi/gen/models/createDatasetExportBodyOneOf.ts +6 -0
- package/openapi/gen/models/createDatasetExportBodyOneOfFormat.ts +2 -0
- package/openapi/gen/models/createDatasetExportBodyOneOfThree.ts +10 -0
- package/openapi/gen/models/createDatasetExportBodyOneOfThreeContract.ts +2 -0
- package/openapi/gen/models/createDatasetExportBodyOneOfThreeFormat.ts +2 -0
- package/openapi/gen/models/createDatasetExportBodyOneOfThreeItemFilter.ts +9 -0
- package/openapi/gen/models/createDatasetExportBodyOneOfThreeItemFilterFieldSourcesItem.ts +6 -0
- package/openapi/gen/models/createDatasetExportBodyOneOfThreeItemFilterFieldSourcesItemSourcesItem.ts +4 -0
- package/openapi/gen/models/createDatasetExportBodyOneOfThreeItemFilterFieldSourcesItemSourcesItemOneOf.ts +5 -0
- package/openapi/gen/models/createDatasetExportBodyOneOfThreeItemFilterFieldSourcesItemSourcesItemOneOfTwo.ts +5 -0
- package/openapi/gen/models/createDatasetExportBodyOneOfThreeItemFilterFieldValuesItem.ts +12 -0
- package/openapi/gen/models/createDatasetExportBodyOneOfThreeItemFilterFieldValuesItemOperator.ts +2 -0
- package/openapi/gen/models/createDatasetExportBodyOneOfThreeItemFilterFieldValuesItemSourcesItem.ts +4 -0
- package/openapi/gen/models/createDatasetExportBodyOneOfThreeItemFilterFieldValuesItemSourcesItemOneOf.ts +5 -0
- package/openapi/gen/models/createDatasetExportBodyOneOfThreeItemFilterFieldValuesItemSourcesItemOneOfTwo.ts +5 -0
- package/openapi/gen/models/createDatasetExportBodyOneOfThreeItemFilterFieldValuesItemValue.ts +2 -0
- package/openapi/gen/models/createProjectModelBody.ts +8 -0
- package/openapi/gen/models/createProjectModelBodyDescription.ts +2 -0
- package/openapi/gen/models/datasetExportReport.ts +9 -0
- package/openapi/gen/models/datasetExportReportErrorsItem.ts +5 -0
- package/openapi/gen/models/datasetExportReportTokens.ts +7 -0
- package/openapi/gen/models/exportJob.ts +2 -0
- package/openapi/gen/models/exportJobReport.ts +3 -0
- package/openapi/gen/models/generationNodeSettingsAudienceAndPricingOneOfOneone.ts +1 -0
- package/openapi/gen/models/index.ts +64 -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/sampleDatasetItemsBody.ts +4 -0
- package/openapi/gen/models/sampleDatasetItemsBodyAnyOf.ts +8 -0
- package/openapi/gen/models/sampleDatasetItemsBodyAnyOfItemFilter.ts +9 -0
- package/openapi/gen/models/sampleDatasetItemsBodyAnyOfItemFilterFieldSourcesItem.ts +6 -0
- package/openapi/gen/models/sampleDatasetItemsBodyAnyOfItemFilterFieldSourcesItemSourcesItem.ts +4 -0
- package/openapi/gen/models/sampleDatasetItemsBodyAnyOfItemFilterFieldSourcesItemSourcesItemOneOf.ts +5 -0
- package/openapi/gen/models/sampleDatasetItemsBodyAnyOfItemFilterFieldSourcesItemSourcesItemOneOfTwo.ts +5 -0
- package/openapi/gen/models/sampleDatasetItemsBodyAnyOfItemFilterFieldValuesItem.ts +12 -0
- package/openapi/gen/models/sampleDatasetItemsBodyAnyOfItemFilterFieldValuesItemOperator.ts +2 -0
- package/openapi/gen/models/sampleDatasetItemsBodyAnyOfItemFilterFieldValuesItemSourcesItem.ts +4 -0
- package/openapi/gen/models/sampleDatasetItemsBodyAnyOfItemFilterFieldValuesItemSourcesItemOneOf.ts +5 -0
- package/openapi/gen/models/sampleDatasetItemsBodyAnyOfItemFilterFieldValuesItemSourcesItemOneOfTwo.ts +5 -0
- package/openapi/gen/models/sampleDatasetItemsBodyAnyOfItemFilterFieldValuesItemValue.ts +2 -0
- package/openapi/gen/models/sampleDatasetItemsBodyAnyOfOnethree.ts +8 -0
- package/openapi/gen/models/sampleDatasetItemsBodyAnyOfOnethreeItemFilter.ts +9 -0
- package/openapi/gen/models/sampleDatasetItemsBodyAnyOfOnethreeItemFilterFieldSourcesItem.ts +6 -0
- package/openapi/gen/models/sampleDatasetItemsBodyAnyOfOnethreeItemFilterFieldSourcesItemSourcesItem.ts +4 -0
- package/openapi/gen/models/sampleDatasetItemsBodyAnyOfOnethreeItemFilterFieldSourcesItemSourcesItemOneOf.ts +5 -0
- package/openapi/gen/models/sampleDatasetItemsBodyAnyOfOnethreeItemFilterFieldSourcesItemSourcesItemOneOfTwo.ts +5 -0
- package/openapi/gen/models/sampleDatasetItemsBodyAnyOfOnethreeItemFilterFieldValuesItem.ts +12 -0
- package/openapi/gen/models/sampleDatasetItemsBodyAnyOfOnethreeItemFilterFieldValuesItemOperator.ts +2 -0
- package/openapi/gen/models/sampleDatasetItemsBodyAnyOfOnethreeItemFilterFieldValuesItemSourcesItem.ts +4 -0
- package/openapi/gen/models/sampleDatasetItemsBodyAnyOfOnethreeItemFilterFieldValuesItemSourcesItemOneOf.ts +5 -0
- package/openapi/gen/models/sampleDatasetItemsBodyAnyOfOnethreeItemFilterFieldValuesItemSourcesItemOneOfTwo.ts +5 -0
- package/openapi/gen/models/sampleDatasetItemsBodyAnyOfOnethreeItemFilterFieldValuesItemValue.ts +2 -0
- package/openapi/gen/models/sampleJob.ts +13 -0
- package/openapi/gen/models/sampleJobStatus.ts +2 -0
- package/openapi/gen/models/sampleResult.ts +5 -0
- package/openapi/gen/models/validatePipelineGraphBodyGraphNodesItemOneOfThreeoneSettingsAudienceAndPricingOneOfOneone.ts +1 -0
- package/package.json +23 -2
- package/skills/catalog.json +14 -6
- package/skills/toloka-build/SKILL.md +34 -12
- package/skills/toloka-build/references/node-semantics.md +27 -24
- package/skills/toloka-build/references/workflow-and-cli.md +67 -27
- package/skills/toloka-report-issue/references/report-template.md +1 -1
- package/src/checks/ast.ts +32 -0
- package/src/checks/index.ts +14 -0
- package/src/checks/postprocess-collisions.ts +145 -0
- package/src/checks/task-ui-declarations.generated.ts +19 -0
- package/src/checks/task-ui-typecheck.ts +345 -0
- package/src/checks/typescript-vfs.d.ts +26 -0
- package/src/checks/typescript-vfs.js +5 -0
- package/src/cli/analytics.ts +1 -0
- package/src/cli/check.ts +977 -0
- package/src/cli/data.ts +148 -25
- package/src/cli/models.ts +188 -0
- package/src/cli/program.ts +246 -20
- package/src/cli/project-context.ts +14 -4
- package/src/cli/runs.ts +21 -11
- package/src/cli/skill.ts +17 -6
- package/src/client.ts +109 -35
- package/src/errors.ts +25 -10
- package/src/testing/code-node.ts +160 -0
- package/src/testing/index.ts +15 -0
- package/src/testing/task-ui-browser.ts +120 -0
- package/src/testing/task-ui-validation.ts +135 -0
- package/src/testing/task-ui.ts +557 -0
- package/src/testing/test-evidence.ts +170 -0
- package/src/testing/types.ts +141 -0
- package/src/transport.ts +16 -13
|
@@ -26,14 +26,17 @@ Treat the three layers differently:
|
|
|
26
26
|
|
|
27
27
|
## Initialize the source root
|
|
28
28
|
|
|
29
|
-
|
|
29
|
+
For a first-time guided session, read `https://platform.toloka.ai/onboarding.md` first. Keep its stage order and checkpoints; use this reference for the commands and source-first invariants inside those stages.
|
|
30
|
+
|
|
31
|
+
Install the SDK before invoking project commands:
|
|
30
32
|
|
|
31
33
|
```sh
|
|
32
34
|
bun add --dev @toloka-platform/sdk
|
|
33
|
-
toloka init
|
|
34
35
|
```
|
|
35
36
|
|
|
36
|
-
|
|
37
|
+
Select or create the intended platform project before `toloka init` when the checkout should be bound immediately; follow the adoption or scratch recipe below. Running `init` without explicit or checkout-local project identity installs only the agent entry points and never adopts the machine registry's current project.
|
|
38
|
+
|
|
39
|
+
`toloka init` checks that `@toloka-platform/sdk` is declared in `dependencies` or `devDependencies`, then maintains one `<toloka>` Agent Bootstrap block in `AGENTS.md` and writes minimal pointers to it: a Claude skill at `.claude/skills/toloka-bootstrap/SKILL.md` when `.claude/` exists (or when no agent configuration exists at all) and an always-applied Cursor rule at `.cursor/rules/toloka-bootstrap.mdc` when `.cursor/rules/` exists. It writes `toloka.json` only from an explicit project flag/environment value or an existing checkout binding.
|
|
37
40
|
|
|
38
41
|
The bootstrap is small: it tells an agent to run `toloka --help`, search the bundled Skill Catalog with `toloka skill search <query>`, install the match with `toloka skill install <skill-id>`, and read `.agents/skills/<skill-id>/SKILL.md`. This skill is that installed copy. Commands warn on stderr when a generated legacy entry point is still present or the recorded bootstrap version differs from the installed SDK. Help, `init`, `report`, `project url`, and version invocations skip this checkout-dependent warning. The warning never blocks a command; re-run `toloka init` to refresh the bootstrap and its pointers.
|
|
39
42
|
|
|
@@ -84,7 +87,7 @@ toloka --version
|
|
|
84
87
|
toloka login
|
|
85
88
|
toloka logout
|
|
86
89
|
toloka auth-status
|
|
87
|
-
toloka project list
|
|
90
|
+
toloka project list [--limit <n>]
|
|
88
91
|
toloka project add [alias] [projectId]
|
|
89
92
|
toloka project switch <alias>
|
|
90
93
|
toloka project current
|
|
@@ -93,6 +96,7 @@ toloka project remove <alias>
|
|
|
93
96
|
toloka project create [alias] [--name <name>] [--team-id <id>]
|
|
94
97
|
toloka init
|
|
95
98
|
toloka import [projectId]
|
|
99
|
+
toloka check [pipeline] [--node <name-or-id>] [--json] [--watch]
|
|
96
100
|
toloka validate
|
|
97
101
|
toloka plan
|
|
98
102
|
toloka apply
|
|
@@ -102,60 +106,76 @@ toloka dataset list
|
|
|
102
106
|
toloka dataset fields <name>
|
|
103
107
|
toloka dataset load <name> --out <file> [--limit <n>] [--timeout <ms>]
|
|
104
108
|
toloka dataset upload <name> <file>
|
|
105
|
-
toloka pipeline list
|
|
109
|
+
toloka pipeline list [--limit <n>]
|
|
106
110
|
toloka pipeline run [name] [--wait] [--timeout <ms>]
|
|
107
111
|
toloka pipeline status [name]
|
|
108
112
|
toloka pipeline runs [name] [--limit <n>]
|
|
109
113
|
toloka pipeline cost [name] [--item-limit <n>]
|
|
110
114
|
toloka pipeline results <runId> [--limit <n>] [--failed]
|
|
111
115
|
toloka pipeline cancel <runId>
|
|
116
|
+
toloka base-model list [--json]
|
|
117
|
+
toloka base-model get <id> [--json]
|
|
118
|
+
toloka model list [--json]
|
|
119
|
+
toloka model get <name-or-id> [--json]
|
|
120
|
+
toloka model versions <name-or-id> [--json]
|
|
121
|
+
toloka model create <name> --base <base-model-id> [--description <text>] [--json]
|
|
122
|
+
toloka model archive <name-or-id> [--json]
|
|
112
123
|
toloka skill list [--json]
|
|
113
124
|
toloka skill search <query> [--json]
|
|
114
125
|
toloka skill install <skill-id> [--force]
|
|
115
126
|
toloka report <markdown> [--authored-by human|agent] [--confirm]
|
|
116
127
|
```
|
|
117
128
|
|
|
118
|
-
Select the platform project with `toloka project add` or `toloka project create` before `import`, `plan`, or `apply`. `init`
|
|
129
|
+
Select the platform project with `toloka project add` or `toloka project create` before `import`, `plan`, or `apply`. For `init`, `toloka --project-id <id> init`, `TOLOKA_PROJECT_ID`, and `POTATO_PROJECT_ID` bind an empty checkout directly without changing the machine registry. `project create` also writes `toloka.json`; `project add` changes only the machine registry, so follow it with `import <projectId>` for an existing project or an explicit project ID on `init` for an empty one. Confirm `toloka project current` and `toloka.json` resolve to the intended ID before authoring.
|
|
119
130
|
|
|
120
131
|
`toloka project url` prints only the selected project's platform UI URL. Use it when the user needs a working link to inspect the live project.
|
|
121
132
|
|
|
122
|
-
`toloka pipeline cost [name] [--item-limit <n>]` forecasts what a run of a live pipeline would bill. It does not start a run
|
|
133
|
+
`toloka pipeline cost [name] [--item-limit <n>]` forecasts what a run of a live, validated pipeline would bill. It does not start a run or change the graph. If any billed node is marked `skipped`, the displayed total is incomplete and must not be presented as the pipeline price. Compute and label a labor floor for each skipped billed node as `items × pricePerTask × annotations per item`, using `1` for generation and `overlap` for quorum. Also report `maxEntities`, quality-criteria count, and only exclusions applicable to the authored QA and workflow. Apply accepting a rate does not prove its final billed value.
|
|
134
|
+
|
|
135
|
+
## Manage model checkpoints
|
|
136
|
+
|
|
137
|
+
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.
|
|
123
138
|
|
|
124
139
|
## Adopt an existing live project
|
|
125
140
|
|
|
126
|
-
1.
|
|
127
|
-
2. Run `toloka project add <alias> <projectId
|
|
128
|
-
3. Run `toloka import
|
|
129
|
-
4.
|
|
130
|
-
5.
|
|
131
|
-
6.
|
|
141
|
+
1. Install `@toloka-platform/sdk` in the intended source root.
|
|
142
|
+
2. Run `toloka project add <alias> <projectId>`, or `toloka project switch <alias>` when the project is already registered.
|
|
143
|
+
3. Run `toloka import` into the empty source root.
|
|
144
|
+
4. Run `toloka init` to install or refresh the agent entry points without rebinding project identity.
|
|
145
|
+
5. Confirm `toloka.json.projectId` equals `toloka project current`, then run `toloka plan` before editing and require `No changes.`. A non-empty baseline is importer or live-state drift to understand separately from your change.
|
|
146
|
+
6. Inspect every generated `pipelines/**/pipeline.ts` dependency, preserve constructor `id` options that bind source to the existing graph, and make the smallest source change.
|
|
147
|
+
7. Validate offline, plan, apply, and plan again. Never copy imported IDs to unrelated declarations or invent replacements.
|
|
132
148
|
|
|
133
149
|
Import refuses an existing `pipelines/` directory. Pass `--force` to replace it with the live graph as TypeScript. Never use `--force` as a harmless refresh over uncommitted source work.
|
|
134
150
|
|
|
135
151
|
## Author against a project from scratch
|
|
136
152
|
|
|
137
|
-
1. Run `toloka
|
|
138
|
-
2. Run `toloka project
|
|
153
|
+
1. Run `toloka project create <alias>` for a new project, or `toloka project add <alias> <projectId>` for an existing empty project.
|
|
154
|
+
2. Run `toloka init` after creation. For an existing empty project, run `toloka --project-id <projectId> init`. Then require `toloka project current` and `toloka.json` to name the same project.
|
|
139
155
|
3. Create `pipelines/<name>/pipeline.ts` and its imported modules.
|
|
140
156
|
4. Default-export `definePipeline({ dataset, fields, nodes, connections })`.
|
|
141
|
-
5.
|
|
142
|
-
6. Run `toloka
|
|
143
|
-
7.
|
|
144
|
-
8. Run `toloka apply
|
|
157
|
+
5. Add typed node and pipeline-flow tests, then run `toloka check`.
|
|
158
|
+
6. Run `toloka validate`; a not-yet-created pipeline is reported as skipped, not server-validated.
|
|
159
|
+
7. Run `toloka plan` and resolve missing secrets or immutable-field conflicts.
|
|
160
|
+
8. Run `toloka apply`; it reruns `toloka check` before any platform write.
|
|
145
161
|
9. Require a final `toloka plan` to print `No changes.`
|
|
146
162
|
|
|
147
163
|
On first apply, the CLI creates each missing named dataset, adds fields required by the authored graphs, creates each missing pipeline, attaches its dataset, and records the resolved IDs. Two pipelines that name the same dataset contribute the union of their required fields.
|
|
148
164
|
|
|
149
|
-
##
|
|
165
|
+
## Check locally and validate against the platform
|
|
150
166
|
|
|
151
|
-
Run the
|
|
167
|
+
Run the complete local gate first, then validate every bound pipeline remotely:
|
|
152
168
|
|
|
153
169
|
```sh
|
|
154
|
-
|
|
170
|
+
toloka check
|
|
155
171
|
toloka validate
|
|
156
172
|
```
|
|
157
173
|
|
|
158
|
-
`toloka
|
|
174
|
+
`toloka check` requires no credentials or live pipeline. It loads source, runs the project TypeScript compiler, typechecks every Task UI against the SDK's bundled declarations, runs Vitest, and compares passing typed test evidence with every checkable node and pipeline. Use `toloka check <pipeline> --node <name-or-id>` for a focused edit loop, `--watch` for serialized reruns, and `--json` for automation. `missing` means required evidence was not produced; `failed` means a check ran and failed; `partial` means hardware-backed behavior remains unverified; `infrastructure_error` means a required tool, browser, or launcher could not run. All four block apply.
|
|
175
|
+
|
|
176
|
+
With `--watch --json`, each completed rerun writes one JSON line. Do not start another watcher while one is active.
|
|
177
|
+
|
|
178
|
+
`toloka validate` posts each authored graph to the platform. Distinguish source/spec errors, which require source changes; `not_ran_node_check`, which describes a separate Remote Node Check; and warnings. It does not write the graph. A pipeline that is not on the platform yet is skipped until `toloka apply` creates it. When plan adds a dataset field used by an already-live pipeline, pre-apply validation can report that exact field as `misconfigured_node_form_component` because the column is not live yet. Treat it as expected only when the reviewed plan adds the same field, then require post-apply validation to clear the source/spec error.
|
|
159
179
|
|
|
160
180
|
The authored API still supplies extra local safety:
|
|
161
181
|
|
|
@@ -164,7 +184,7 @@ The authored API still supplies extra local safety:
|
|
|
164
184
|
- It rejects a `connect()` endpoint whose node is absent from `nodes`.
|
|
165
185
|
- Typed ports prevent reversing input and output handles.
|
|
166
186
|
|
|
167
|
-
|
|
187
|
+
`toloka apply` runs the full Local Check Gate again before resolving credentials or writing platform state. Plan compares source with live resources but does not submit the desired graph for acceptance. Apply can still expose graph-wide references, layout identity, or concurrent-version assumptions that only the backend owns.
|
|
168
188
|
|
|
169
189
|
## Read and apply a plan
|
|
170
190
|
|
|
@@ -178,6 +198,20 @@ Treat every plan line as evidence of what apply will attempt.
|
|
|
178
198
|
|
|
179
199
|
Apply preserves existing live layout and the root's live item filter. Do not add either to authored source. For graph replacement, apply reads the live graph version and sends it as `expectedVersion`; never put a version in source or state yourself.
|
|
180
200
|
|
|
201
|
+
## Test pipeline nodes locally
|
|
202
|
+
|
|
203
|
+
Register every code-node behavior with `test.code(pipeline, node, name, callback)`. Its callback receives a bound `run(inputs, options?)` helper with the same Python execution contract as `runCodeNode`; the test counts only after the bound executor runs, an assertion executes, and the callback passes.
|
|
204
|
+
|
|
205
|
+
Before choosing component names or props, call `ui.componentDocumentation()` in a local `@toloka-platform/sdk/testing` scenario. A component's contract is its returned `propsType`, `valueType`, and `example`. For structured or array-valued Fields, derive the dataset field schema from `valueType`, then feed the documented example value through `setValue`, `getValue`, and `submit`; never translate undocumented property names or replace missing values with defaults. Register required human-work behavior with `test.ui(pipeline, node, name, callback)`. Its callback receives bound `render(options?)`; the test counts only after a successful `submit`, at least one assertion, and a passing callback.
|
|
206
|
+
|
|
207
|
+
Register the representative connected flow with `test.pipeline(pipeline, name, callback)`. When a local dataset file is available, use its first row as the initial item state; redact sensitive values while preserving field names, nesting, optionality, and media/value shapes. Use synthetic input only when no local row exists. Use the callback's bound `runCode` and `renderUi` helpers, merge only declared node outputs, and assert the final `root.completed` fields. Keep a linear happy path in one test; use separate scenarios for match branches, optional routes, and failures.
|
|
208
|
+
|
|
209
|
+
```sh
|
|
210
|
+
toloka check
|
|
211
|
+
```
|
|
212
|
+
|
|
213
|
+
Plain Vitest tests still run and can fail the command, but only typed Toloka wrappers satisfy required node and pipeline coverage. Use synthetic or redacted data. `preprocess` and `postprocess` remain runtime JavaScript function bodies, not TypeScript.
|
|
214
|
+
|
|
181
215
|
## Add dataset rows
|
|
182
216
|
|
|
183
217
|
Declare the fields in source, then upload rows:
|
|
@@ -186,7 +220,9 @@ Declare the fields in source, then upload rows:
|
|
|
186
220
|
toloka dataset upload <dataset> <file>
|
|
187
221
|
```
|
|
188
222
|
|
|
189
|
-
The
|
|
223
|
+
The upload file path resolves from the command process's working directory, not from `--source-root`. Use an absolute path when the command runs elsewhere.
|
|
224
|
+
|
|
225
|
+
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
226
|
|
|
191
227
|
If a later upload fails with a schema-incompatibility error, inspect the inferred schema from the first successful upload:
|
|
192
228
|
|
|
@@ -194,7 +230,7 @@ If a later upload fails with a schema-incompatibility error, inspect the inferre
|
|
|
194
230
|
toloka dataset fields <dataset>
|
|
195
231
|
```
|
|
196
232
|
|
|
197
|
-
That prints `field()` declarations for the top-level inferred columns.
|
|
233
|
+
That prints `field()` declarations for the top-level inferred columns. Use the output to recover stored names and base types, but do not copy it wholesale over stricter source schemas: inference can omit enum, length, range, and format constraints. It does not write source or mutate the dataset.
|
|
198
234
|
|
|
199
235
|
Keep item files outside the source tree or gitignored. Never embed sample or production rows in `pipeline.ts` to make reconciliation create them.
|
|
200
236
|
|
|
@@ -258,9 +294,13 @@ Use the exact name reported by plan. Set it with `toloka secret set`, rerun plan
|
|
|
258
294
|
|
|
259
295
|
A 409 means live graph state changed after apply read it. Run plan again, compare the new live diff with the source, incorporate intentional live changes into source when required, and apply once more. Never add `expectedVersion` to source and never loop retries.
|
|
260
296
|
|
|
297
|
+
### Unexplained node drift after apply
|
|
298
|
+
|
|
299
|
+
Object settings preserve omitted live keys, but list-valued settings such as conditions, predicates, entities, steps, and rules can be compared as whole values after the server adds defaults inside an element. If plan reports an unnamed node change after a clean apply, run `toloka import` into a scratch directory outside the source root, compare that node with source, and declare only the materialized key needed for convergence. Delete the scratch directory afterward and never re-apply blindly.
|
|
300
|
+
|
|
261
301
|
### Server 400 after a clean validate
|
|
262
302
|
|
|
263
|
-
|
|
303
|
+
A new pipeline is skipped by `toloka validate` until it exists remotely, and apply can create its dataset and fields before pipeline import returns a 400. Fix the reported path or reason, then run plan again. If only the pipeline addition remains, continue from that diff; do not recreate or rename resources already created. Check connection endpoints, duplicate or unknown imported IDs, field references, and accidentally authored layout. Never bypass reconciliation with a raw API call.
|
|
264
304
|
|
|
265
305
|
## Use the shipped example
|
|
266
306
|
|
|
@@ -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.7 (`toloka --version`), Bun 1.4.0, macOS 15
|
|
30
30
|
- Platform: https://platform.toloka.ai
|
|
31
31
|
|
|
32
32
|
## Redactions
|
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { JSXAttribute } from "oxc-parser";
|
|
2
|
+
import { parseSync } from "oxc-parser";
|
|
3
|
+
|
|
4
|
+
// Sources are parsed with the same wrapping buildCompositeSource (task-ui-typecheck.ts) injects — if that
|
|
5
|
+
// wrapping changes, change parseProgram's callers with it.
|
|
6
|
+
export const parseProgram = (filename: string, source: string, lang: "ts" | "tsx") => {
|
|
7
|
+
const result = parseSync(filename, source, { lang, preserveParens: false });
|
|
8
|
+
|
|
9
|
+
return result.errors.some((error) => error.severity === "Error") ? null : result.program;
|
|
10
|
+
};
|
|
11
|
+
|
|
12
|
+
export const resolveAttributeString = (attribute: JSXAttribute | undefined): string | null => {
|
|
13
|
+
const value = attribute?.value;
|
|
14
|
+
|
|
15
|
+
if (!value) return null;
|
|
16
|
+
|
|
17
|
+
if (value.type === "Literal") return typeof value.value === "string" ? value.value : null;
|
|
18
|
+
|
|
19
|
+
if (value.type === "JSXExpressionContainer") {
|
|
20
|
+
const expression = value.expression;
|
|
21
|
+
|
|
22
|
+
if (expression.type === "Literal" && typeof expression.value === "string") {
|
|
23
|
+
return expression.value;
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
if (expression.type === "TemplateLiteral" && expression.expressions.length === 0) {
|
|
27
|
+
return expression.quasis[0]?.value.cooked ?? null;
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
return null;
|
|
32
|
+
};
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
export { findPostprocessKeyCollisions } from "./postprocess-collisions";
|
|
2
|
+
export {
|
|
3
|
+
createBundledTaskUiTypechecker,
|
|
4
|
+
createTaskUiTypechecker,
|
|
5
|
+
taskUiDeclarationSnapshot,
|
|
6
|
+
} from "./task-ui-typecheck";
|
|
7
|
+
export type {
|
|
8
|
+
TaskUiBlockKey,
|
|
9
|
+
TaskUiDeclarationSource,
|
|
10
|
+
TaskUiDiagnostic,
|
|
11
|
+
TaskUiDiagnosticsReport,
|
|
12
|
+
TaskUiSource,
|
|
13
|
+
TaskUiTypechecker,
|
|
14
|
+
} from "./task-ui-typecheck";
|
|
@@ -0,0 +1,145 @@
|
|
|
1
|
+
import type { BlockStatement, Expression, JSXAttribute, ObjectExpression } from "oxc-parser";
|
|
2
|
+
import { Visitor } from "oxc-parser";
|
|
3
|
+
|
|
4
|
+
import { parseProgram, resolveAttributeString } from "./ast";
|
|
5
|
+
import type { TaskUiDiagnostic, TaskUiSource } from "./task-ui-typecheck";
|
|
6
|
+
|
|
7
|
+
// Collisions in conditional return branches are errors too: a submission whose data reaches the
|
|
8
|
+
// branch still dies at getData().
|
|
9
|
+
export const findPostprocessKeyCollisions = (
|
|
10
|
+
ui: Pick<TaskUiSource, "code" | "postprocess">,
|
|
11
|
+
): TaskUiDiagnostic[] => {
|
|
12
|
+
const returnedKeys = extractPostprocessReturnedKeys(ui.postprocess);
|
|
13
|
+
if (!returnedKeys || returnedKeys.size === 0) return [];
|
|
14
|
+
const bindings = extractFieldPathBindings(ui.code);
|
|
15
|
+
if (!bindings || bindings.size === 0) return [];
|
|
16
|
+
|
|
17
|
+
return [...bindings]
|
|
18
|
+
.filter(([key]) => returnedKeys.has(key))
|
|
19
|
+
.sort(([keyA], [keyB]) => keyA.localeCompare(keyB))
|
|
20
|
+
.map(([key, path]) => {
|
|
21
|
+
const binding =
|
|
22
|
+
path === key
|
|
23
|
+
? `path="${path}"`
|
|
24
|
+
: `path="${path}" — a dotted path binds its top-level key "${key}"`;
|
|
25
|
+
return {
|
|
26
|
+
severity: "error",
|
|
27
|
+
text:
|
|
28
|
+
`Postprocess returns the key "${key}", but a Field component is bound to it (${binding}). ` +
|
|
29
|
+
`Every submission of this interface fails at runtime with: Postprocess cannot write key '${key}' because a component is bound to it. ` +
|
|
30
|
+
`Fix: bind the Field to a raw key (e.g. path="raw_${key}") and derive "${key}" in postprocess, or remove "${key}" from the postprocess return.`,
|
|
31
|
+
};
|
|
32
|
+
});
|
|
33
|
+
};
|
|
34
|
+
|
|
35
|
+
const extractFieldPathBindings = (code: string): Map<string, string> | null => {
|
|
36
|
+
const program = parseProgram("code.tsx", `(\n${code}\n)`, "tsx");
|
|
37
|
+
if (!program) return null;
|
|
38
|
+
const bindings = new Map<string, string>();
|
|
39
|
+
const visitor = new Visitor({
|
|
40
|
+
JSXOpeningElement(node) {
|
|
41
|
+
let name = node.name;
|
|
42
|
+
while (name.type === "JSXMemberExpression") name = name.object;
|
|
43
|
+
if (name.type !== "JSXIdentifier" || name.name !== "Field") return;
|
|
44
|
+
const pathAttribute = node.attributes.find(
|
|
45
|
+
(attribute): attribute is JSXAttribute =>
|
|
46
|
+
attribute.type === "JSXAttribute" &&
|
|
47
|
+
attribute.name.type === "JSXIdentifier" &&
|
|
48
|
+
attribute.name.name === "path",
|
|
49
|
+
);
|
|
50
|
+
const path = resolveAttributeString(pathAttribute);
|
|
51
|
+
if (!path) return;
|
|
52
|
+
const key = path.split(/[.[]/u)[0];
|
|
53
|
+
if (key && !bindings.has(key)) bindings.set(key, path);
|
|
54
|
+
},
|
|
55
|
+
});
|
|
56
|
+
visitor.visit(program);
|
|
57
|
+
return bindings;
|
|
58
|
+
};
|
|
59
|
+
|
|
60
|
+
const extractPostprocessReturnedKeys = (postprocess: string): Set<string> | null => {
|
|
61
|
+
const program = parseProgram("postprocess.ts", `(() => {\n${postprocess}\n})`, "ts");
|
|
62
|
+
if (!program) return null;
|
|
63
|
+
const statement = program.body.length === 1 ? program.body[0] : undefined;
|
|
64
|
+
// postprocess text can escape the wrapper braces yet still parse — `}) , (() => {` turns the
|
|
65
|
+
// expression into a SequenceExpression — so only the intact wrapper shape is analyzable
|
|
66
|
+
if (
|
|
67
|
+
statement?.type !== "ExpressionStatement" ||
|
|
68
|
+
statement.expression.type !== "ArrowFunctionExpression" ||
|
|
69
|
+
statement.expression.body.type !== "BlockStatement"
|
|
70
|
+
) {
|
|
71
|
+
return null;
|
|
72
|
+
}
|
|
73
|
+
const keys = new Set<string>();
|
|
74
|
+
for (const returned of collectOwnReturnArguments(statement.expression.body)) {
|
|
75
|
+
collectObjectLiteralKeys(returned, keys);
|
|
76
|
+
}
|
|
77
|
+
return keys;
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
const functionNodeTypes = new Set([
|
|
81
|
+
"ArrowFunctionExpression",
|
|
82
|
+
"FunctionExpression",
|
|
83
|
+
"FunctionDeclaration",
|
|
84
|
+
]);
|
|
85
|
+
|
|
86
|
+
const collectOwnReturnArguments = (body: BlockStatement): Expression[] => {
|
|
87
|
+
const returned: Expression[] = [];
|
|
88
|
+
const visit = (node: unknown): void => {
|
|
89
|
+
if (!node || typeof node !== "object") return;
|
|
90
|
+
if (Array.isArray(node)) {
|
|
91
|
+
for (const item of node) visit(item);
|
|
92
|
+
return;
|
|
93
|
+
}
|
|
94
|
+
const candidate = node as { type?: unknown } & Record<string, unknown>;
|
|
95
|
+
if (typeof candidate.type === "string") {
|
|
96
|
+
if (functionNodeTypes.has(candidate.type)) return;
|
|
97
|
+
if (candidate.type === "ReturnStatement" && candidate.argument) {
|
|
98
|
+
returned.push(candidate.argument as Expression);
|
|
99
|
+
}
|
|
100
|
+
}
|
|
101
|
+
for (const value of Object.values(candidate)) visit(value);
|
|
102
|
+
};
|
|
103
|
+
visit(body.body);
|
|
104
|
+
return returned;
|
|
105
|
+
};
|
|
106
|
+
|
|
107
|
+
const collectObjectLiteralKeys = (expression: Expression, keys: Set<string>): void => {
|
|
108
|
+
if (expression.type === "ObjectExpression") {
|
|
109
|
+
collectPropertyKeys(expression, keys);
|
|
110
|
+
} else if (expression.type === "ConditionalExpression") {
|
|
111
|
+
collectObjectLiteralKeys(expression.consequent, keys);
|
|
112
|
+
collectObjectLiteralKeys(expression.alternate, keys);
|
|
113
|
+
} else if (expression.type === "LogicalExpression") {
|
|
114
|
+
collectObjectLiteralKeys(expression.left, keys);
|
|
115
|
+
collectObjectLiteralKeys(expression.right, keys);
|
|
116
|
+
} else if (expression.type === "SequenceExpression") {
|
|
117
|
+
const last = expression.expressions.at(-1);
|
|
118
|
+
if (last) collectObjectLiteralKeys(last, keys);
|
|
119
|
+
} else if (
|
|
120
|
+
expression.type === "TSAsExpression" ||
|
|
121
|
+
expression.type === "TSSatisfiesExpression" ||
|
|
122
|
+
expression.type === "TSNonNullExpression" ||
|
|
123
|
+
expression.type === "TSTypeAssertion"
|
|
124
|
+
) {
|
|
125
|
+
collectObjectLiteralKeys(expression.expression, keys);
|
|
126
|
+
}
|
|
127
|
+
};
|
|
128
|
+
|
|
129
|
+
const collectPropertyKeys = (objectExpression: ObjectExpression, keys: Set<string>): void => {
|
|
130
|
+
for (const property of objectExpression.properties) {
|
|
131
|
+
if (property.type === "SpreadElement") {
|
|
132
|
+
if (property.argument.type === "ObjectExpression")
|
|
133
|
+
collectPropertyKeys(property.argument, keys);
|
|
134
|
+
continue;
|
|
135
|
+
}
|
|
136
|
+
if (!property.computed && property.key.type === "Identifier") {
|
|
137
|
+
keys.add(property.key.name);
|
|
138
|
+
} else if (
|
|
139
|
+
property.key.type === "Literal" &&
|
|
140
|
+
(typeof property.key.value === "string" || typeof property.key.value === "number")
|
|
141
|
+
) {
|
|
142
|
+
keys.add(String(property.key.value));
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
};
|