@toloka-platform/sdk 0.1.0-preview.6 → 0.1.0-preview.8
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 +10 -15
- package/dist/cli/toloka.js +209 -121
- 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 +20 -9
- package/docs/cli-v2.md +3 -3
- package/docs/cli.md +44 -16
- package/docs/getting-started.md +7 -12
- package/docs/limitations.md +6 -6
- package/docs/testing-examples.md +248 -0
- package/docs/testing.md +115 -0
- 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 +3 -5
- 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/adjudication_match/node.ts +15 -17
- package/examples/video-pair-rating/nodes/assemble/node.ts +25 -27
- package/examples/video-pair-rating/nodes/cascade/node.ts +24 -26
- package/examples/video-pair-rating/nodes/finalize_overall/node.ts +17 -19
- package/examples/video-pair-rating/nodes/merge_adjudication/node.ts +13 -15
- package/examples/video-pair-rating/nodes/merge_op_adjudication/node.ts +12 -14
- package/examples/video-pair-rating/nodes/op_quorum_resolve/node.ts +13 -15
- 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/nodes/route_adjudication/node.ts +1 -3
- package/examples/video-pair-rating/nodes/route_op_adjudication/node.ts +5 -7
- package/examples/video-pair-rating/nodes/route_overall/node.ts +1 -3
- package/examples/video-pair-rating/nodes/shuffle_ab/node.ts +11 -13
- package/examples/video-pair-rating/pipeline.browser.integration.spec.ts +267 -0
- package/openapi/gen/api.ts +48 -11
- 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/datasetExportReport.ts +9 -0
- package/openapi/gen/models/datasetExportReportErrorsItem.ts +5 -0
- package/openapi/gen/models/datasetExportReportTokens.ts +7 -0
- package/openapi/gen/models/datasetItem.ts +0 -2
- package/openapi/gen/models/exportJob.ts +2 -0
- package/openapi/gen/models/exportJobReport.ts +3 -0
- package/openapi/gen/models/index.ts +48 -1
- 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/package.json +23 -2
- package/skills/catalog.json +14 -6
- package/skills/toloka-build/SKILL.md +35 -12
- package/skills/toloka-build/references/node-semantics.md +32 -29
- package/skills/toloka-build/references/workflow-and-cli.md +61 -27
- package/skills/toloka-report-issue/references/report-template.md +1 -1
- package/src/authoring/index.ts +27 -26
- 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 +9 -19
- package/src/cli/auto-update.ts +367 -0
- package/src/cli/boolean-flag.ts +13 -0
- package/src/cli/check.ts +977 -0
- package/src/cli/credentials.ts +3 -0
- package/src/cli/program.ts +184 -20
- package/src/cli/project-context.ts +14 -4
- package/src/cli/runs.ts +21 -11
- package/src/cli/settings.ts +93 -0
- package/src/cli/skill.ts +17 -6
- package/src/importer.ts +12 -16
- 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/openapi/gen/models/datasetItemDuplicateOfSubmissionId.ts +0 -2
|
@@ -20,21 +20,21 @@ Choose by behavior, not by the data format:
|
|
|
20
20
|
|
|
21
21
|
Use the typed node properties; never write handle strings yourself:
|
|
22
22
|
|
|
23
|
-
| Node
|
|
24
|
-
|
|
|
25
|
-
| `root`
|
|
26
|
-
| `code`
|
|
27
|
-
| `generation` | `annotate.input`
|
|
28
|
-
| `quorum`
|
|
29
|
-
| `match`
|
|
23
|
+
| Node | Input | Output |
|
|
24
|
+
| ------------ | ---------------------- | ---------------------------------------------------------- |
|
|
25
|
+
| `root` | `collection.completed` | `collection.started` |
|
|
26
|
+
| `code` | `transform.input` | `transform.output` |
|
|
27
|
+
| `generation` | `annotate.input` | `annotate.output` |
|
|
28
|
+
| `quorum` | `rate.input` | `rate.output` |
|
|
29
|
+
| `match` | `route.input` | `route.matched(0)`, `route.matched(1)`, …, `route.default` |
|
|
30
30
|
|
|
31
31
|
Connect them in the direction of data flow:
|
|
32
32
|
|
|
33
33
|
```ts
|
|
34
|
-
connect(collection.started, transform.input)
|
|
35
|
-
connect(transform.output, route.input)
|
|
36
|
-
connect(route.matched(0), annotate.input)
|
|
37
|
-
connect(annotate.output, collection.completed)
|
|
34
|
+
connect(collection.started, transform.input);
|
|
35
|
+
connect(transform.output, route.input);
|
|
36
|
+
connect(route.matched(0), annotate.input);
|
|
37
|
+
connect(annotate.output, collection.completed);
|
|
38
38
|
```
|
|
39
39
|
|
|
40
40
|
`match.matched(index)` corresponds by index to `settings.conditions[index]`. The first matching condition wins; `default` receives items that match none.
|
|
@@ -52,14 +52,13 @@ The public authoring shape is `RootAuthoringSettings`:
|
|
|
52
52
|
- Each entry can be a `Field` or `{ field, optional }`; a bare `Field` is required.
|
|
53
53
|
- `itemLimit` exists in both `RootNodeSettings` and `RootAuthoringSettings`. Do not invent a limit; author one only when the user explicitly requests a bounded test or batch. Apply preserves a live `itemLimit` unless source declares its own.
|
|
54
54
|
|
|
55
|
-
|
|
56
55
|
The dataset name belongs in `definePipeline({ dataset: 'name', ... })`, not in the root settings. `toloka apply` resolves or creates the named dataset and supplies its ID.
|
|
57
56
|
|
|
58
57
|
### Traps
|
|
59
58
|
|
|
60
59
|
- Do not author `datasetId` or `itemFilter`; `RootAuthoringSettings` omits both. `toloka apply` binds the dataset and preserves the live `itemFilter` and `itemLimit` unless source declares its own `itemLimit`. Do not add a limit to source to restore one you saw in the UI.
|
|
61
60
|
|
|
62
|
-
-
|
|
61
|
+
- `completed` may contain any declared field carried on the incoming branch, whether it originated at root input or an upstream output. Never list a field no upstream node provides. Until a run produces results, write-back of a pass-through input is unobserved and must be reported that way.
|
|
63
62
|
- Declare every referenced `Field` in `definePipeline({ fields })`.
|
|
64
63
|
- Dataset fields are immutable after creation. You may add a column, never alter or remove one. On a schema conflict, use a new column name or a new dataset.
|
|
65
64
|
|
|
@@ -76,7 +75,7 @@ Use a downstream `code` node after `quorum` only when the user asks for a derive
|
|
|
76
75
|
Set `CodeAuthoringSettings`, derived from `CodeNodeSettings`, through `code(name, settings)`:
|
|
77
76
|
|
|
78
77
|
- `about`: node `name`, optional `description`, and optional `projectType`.
|
|
79
|
-
- `
|
|
78
|
+
- `input` and `output`: `{ field, optional }` references to declared `Field` objects.
|
|
80
79
|
- `code.code`: Python source.
|
|
81
80
|
- `envVars`: optional static values or secret bindings:
|
|
82
81
|
- `{ type: 'static', name, value }`
|
|
@@ -138,21 +137,23 @@ Use `about.projectType: 'data_collection'` when experts produce new original con
|
|
|
138
137
|
|
|
139
138
|
#### Entities
|
|
140
139
|
|
|
141
|
-
An authored entity
|
|
140
|
+
An authored entity references a declared `field`. Set `entityType`, `optional`, `jsonPath`, `description`, `relations`, and the discriminant:
|
|
142
141
|
|
|
143
142
|
- `dynamic: false` means one value in one annotation; use a path without `[*]` and omit `maxEntities`.
|
|
144
143
|
- `dynamic: true` means repeated values inside one annotation; use exactly one `[*]` in `jsonPath` and set `maxEntities` from 1 through 50.
|
|
145
144
|
- Use `allowedLabels: [{ label, value }]` for fixed choices. Use `SINGLE_LABEL` for exactly one stored value and `MULTI_LABEL` for multiple values.
|
|
146
|
-
- Set
|
|
145
|
+
- Set media duration bounds only when the expert produces the media through an upload or recording output. Input media used for annotation does not require duration bounds.
|
|
147
146
|
- Give every output entity at least one relation to the input it uses: `{ field, rel_type: 'context' | 'references' | 'depends_on' | 'evaluates' }`, or a conditional relation supported by the public type.
|
|
148
147
|
|
|
148
|
+
`entityType` is a closed platform vocabulary even where a generated TypeScript shape is currently broad. Use `CONTEXT` for any non-media input the expert reads, including text, numbers, and objects. Use `SHORT_ANSWER` for short produced text, `RESPONSE` for long produced text, and `EXPLANATION` only for reasoning about another value. Use `SINGLE_LABEL` or `MULTI_LABEL` for fixed choices and the matching media or annotation type for those values. Never invent `TEXT` or another natural-language type name; consult the documented vocabulary when these categories do not fit.
|
|
149
|
+
|
|
149
150
|
Entities enforce format, not meaning. Put types, ranges, cardinality, requiredness, and labels in the field schema and entity. Put accuracy, correctness, completeness, and clarity in quality criteria.
|
|
150
151
|
|
|
151
|
-
For
|
|
152
|
+
For any single-item upload or recording control, the component value is still an array. Declare the output field as a top-level array with `minItems: 1` and `maxItems: 1`, the output entity as `dynamic: true` with `maxEntities: 1` and a `jsonPath` ending in `[*]`, and use the matching media or document entity type. Map the component's documented completed-value link field in `postprocess`. For video, also set both entity duration bounds and align them with the UI component.
|
|
152
153
|
|
|
153
154
|
#### Quality and instructions
|
|
154
155
|
|
|
155
|
-
Write at least one criterion for every output entity. Each criterion needs an authored UUID `id`, `qualityElement`, mutually exclusive and exhaustive `thresholds.pass` and `thresholds.fail`, and `targetEntity: outputField`. Invert the task into an observable check: “tag every car” becomes “every visible car has exactly one tag.” Keep format validation out of criteria.
|
|
156
|
+
Write at least one criterion for every output entity. Each criterion needs an authored UUID `id`, `qualityElement`, mutually exclusive and exhaustive `thresholds.pass` and `thresholds.fail`, and `targetEntity: outputField`. Every criterion must resolve to pass or fail for every item; express edge cases as conditional clauses inside an always-applicable criterion. Criteria judge meaning, not an exact phrase you authored unless the request fixes that phrase. Before apply, walk every sample row through the instruction and all criteria: each criterion must yield one verdict, and no two authored rules may demand different values for the same row. Invert the task into an observable check: “tag every car” becomes “every visible car has exactly one tag.” Keep format validation out of criteria.
|
|
156
157
|
|
|
157
158
|
Use `llmQaInstruction` only when the evaluator needs a specific verification method, authoritative source, tool, or pinned edge case beyond the thresholds. It is evaluator guidance, not labeler guidance, and has no effect in `human_only` mode.
|
|
158
159
|
|
|
@@ -162,7 +163,7 @@ Keep `instruction` actionable and in English. Include the workflow, concepts, qu
|
|
|
162
163
|
|
|
163
164
|
#### Audience and pricing
|
|
164
165
|
|
|
165
|
-
For humans, set `audienceType: 'human'`, `verifiedExperts`, all three audience filters, `pricePerTask`, and `timeForTask`. The public shapes differ by filter: languages use `{ type: 'and' | 'or', values: [{ value: 'English' }] }`; specializations use `{ type: 'and' | 'or', values: [{ value, qualificationId }] }`; countries use `{ type: 'or', values: ['
|
|
166
|
+
For humans, set `audienceType: 'human'`, `verifiedExperts`, all three audience filters, `pricePerTask`, and `timeForTask`. The public shapes differ by filter: languages use `{ type: 'and' | 'or', values: [{ value: 'English' }] }`; specializations use `{ type: 'and' | 'or', values: [{ value, qualificationId }] }`; countries use ISO 3166-1 alpha-2 codes, for example `{ type: 'or', values: ['US'] }`. Verify every country value matches `/^[A-Z]{2}$/`; apply currently accepts arbitrary strings that can match no experts. Start unspecified filters with English, no specializations, and no countries. Do not put specializations on an unverified audience. A specialization's `qualificationId` is platform identity: preserve an imported value, and do not invent one when the SDK cannot discover it.
|
|
166
167
|
|
|
167
168
|
Start with `$0.20` and 120 seconds when the task gives no stronger signal, then change them only for an explicit rate or clearly longer work. The per-expert cap is normally off. For `data_collection`, use a cap of 50 by default; use 1 for one-response-per-expert collection.
|
|
168
169
|
|
|
@@ -185,10 +186,10 @@ Use `reuseAudienceWithNodeId` only with real imported `{ pipelineId, nodeId }` v
|
|
|
185
186
|
- Do not add fields, explanations, comments, or labor the task does not require.
|
|
186
187
|
- Do not choose synthetic generation merely because a model could do the task; require an explicit request.
|
|
187
188
|
- Do not treat the constructor's generated placeholder Training or blank UI as production-ready configuration.
|
|
188
|
-
- Do not pair `projectType: 'general'` with `verifiedExperts: false`. General projects require an ID-verified audience, and the server rejects the graph with `General projects require an ID-verified audience`. `data_collection` supports both modes but requires a
|
|
189
|
+
- Do not pair `projectType: 'general'` with `verifiedExperts: false`. General projects require an ID-verified audience, and the server rejects the graph with `General projects require an ID-verified audience`. `data_collection` supports both modes but requires a hardware upload or recording component supported by the current UI catalog. An annotation task with an unverified audience therefore cannot be expressed: `general` demands verified experts and `data_collection` demands an upload control the task has no use for. When a request asks for one, say so and let the requester choose, rather than quietly flipping a setting.
|
|
189
190
|
- Do not omit `ui.constants`. It is required alongside `code`, `data`, `preprocess`, and `postprocess`; `'{}'` is the right value when there is nothing to pass. Omitting it fails validation with `must have required property 'constants'`, usually reported together with a misleading `type must be equal to constant` from the failed node discrimination.
|
|
190
191
|
- Do not ship a generation node without Training. `onboarding` is mandatory: at least three `steps`, of which at least one carries a `quiz` block. The server rejects fewer with `Onboarding must have at least 3 steps` and `Onboarding must include at least one quiz block`.
|
|
191
|
-
- Do not treat the server's materialized defaults as your configuration. A deployed node reports `qualityAssurance`, `exam`, `reuseAudienceWithNodeId`, and `smartSearch` even when source never set them. Leave them out
|
|
192
|
+
- Do not treat the server's materialized defaults as your configuration. A deployed node reports `qualityAssurance`, `exam`, `reuseAudienceWithNodeId`, and `smartSearch` even when source never set them. Leave them out unless you mean to control them. Omitted top-level object keys are preserved, but defaults materialized inside list elements can still cause drift; use the generic import-and-diff recovery flow.
|
|
192
193
|
|
|
193
194
|
## `quorum`
|
|
194
195
|
|
|
@@ -213,6 +214,8 @@ Quorum is human-only in the current public shape; its `audienceAndPricing.audien
|
|
|
213
214
|
|
|
214
215
|
Configure entities for one labeler's contribution, not for the outer array. A single label per person therefore uses `dynamic: false` and `jsonPath: '$.label'`, while the declared output field is `{ type: 'array', items: { type: 'string' } }`. `dynamic: true` means an array inside each labeler's contribution and therefore produces an array of arrays. Build the UI to submit one value at `$.label`, never `$.label[0]`; the platform assembles the outer array.
|
|
215
216
|
|
|
217
|
+
When a quorum output entity is `optional: true`, its outer field's `items` schema must also accept `null`, for example `{ type: 'array', items: { type: ['string', 'null'] } }`. The platform reserves a null annotation slot when that labeler omits the value and rejects an optional entity backed by non-nullable items.
|
|
218
|
+
|
|
216
219
|
Quorum does not aggregate. It writes all raw annotations back, including rejected ones. Add a downstream `code` node only when the user asks for one consensus value, and make it tolerate arrays longer than `overlap`.
|
|
217
220
|
|
|
218
221
|
#### Explanation-driven quality
|
|
@@ -256,20 +259,20 @@ Use `match` when one item must take one conditional path based on data. Do not u
|
|
|
256
259
|
Set `MatchAuthoringSettings`, derived from `MatchNodeSettings`, through `match(name, settings)`:
|
|
257
260
|
|
|
258
261
|
- `about`: `name`, optional `description`, optional `projectType`.
|
|
259
|
-
- `
|
|
260
|
-
- `conditions`: ordered branches
|
|
262
|
+
- `input`: every declared `Field` referenced by a predicate, each with `optional`.
|
|
263
|
+
- `conditions`: ordered branches with optional `logic: 'and' | 'or'` and required `predicates`.
|
|
261
264
|
- Each authored predicate uses `field`, `jsonPath`, `operator`, and scalar `value`.
|
|
262
265
|
|
|
263
266
|
The current public type accepts only `operator: 'text_eq' | 'text_neq'` and `value: string | number | boolean | null`. Do not copy broader internal operator lists into source. Use `jsonPath: '$'` for the whole scalar or a restricted path into an object.
|
|
264
267
|
|
|
265
|
-
Ordering is semantic: `conditions[0]` routes to `matched(0)`, the first true condition wins, and an implicit `default` handles no match. Every predicate field must also appear in `
|
|
268
|
+
Ordering is semantic: `conditions[0]` routes to `matched(0)`, the first true condition wins, and an implicit `default` handles no match. Every predicate field must also appear in `input`.
|
|
266
269
|
|
|
267
270
|
### Traps
|
|
268
271
|
|
|
269
272
|
- Do not write a default condition; connect `route.default`.
|
|
270
273
|
- Do not assume all matching branches run.
|
|
271
274
|
- Do not use unsupported comparison, set-membership, range, or emptiness operators. Put that logic in `code`, emit a routing key, then compare it with `text_eq` or `text_neq`.
|
|
272
|
-
- Do not reference a field in a predicate without binding it in `
|
|
275
|
+
- Do not reference a field in a predicate without binding it in `input`.
|
|
273
276
|
|
|
274
277
|
## Public-surface gaps
|
|
275
278
|
|
|
@@ -278,14 +281,14 @@ When the requirement depends on one of these capabilities, state the gap instead
|
|
|
278
281
|
- Audience discovery and validation helpers have no SDK or CLI path: valid-language/country/specialization suggestions, audience-size statistics, and recommended-rate calculation.
|
|
279
282
|
- Audience-reuse candidate discovery has no SDK or CLI path. Source can store a known cross-pipeline pointer, but cannot search candidates.
|
|
280
283
|
- Smart Search rendering, source-fingerprint computation, and stale-profile diagnosis have no SDK or CLI path. Source can carry the public profile; it cannot generate the server-owned fingerprint or preview rendered sourcing documents.
|
|
281
|
-
- The
|
|
284
|
+
- The `@toloka-platform/sdk/testing` entry point provides typed `test.code`, `test.ui`, and `test.pipeline` wrappers. `toloka check` runs their evidence with project TypeScript and automatic Task UI typechecking. Missing Remote Node Checks remain separate from this local gate.
|
|
282
285
|
- Training's examples playbooks and automatic course authoring have no SDK/CLI path. Source can set `onboarding`, but cannot request geometry/numeric check templates or validate hands-on examples locally beyond the public graph schema.
|
|
283
286
|
- Exam golden-dataset discovery, creation by symbolic dataset name, dataset lint execution, and generated-golden confirmation flow have no high-level SDK authoring path. `exam.goldenDatasetId` requires a real ID.
|
|
284
|
-
- Per-node
|
|
285
|
-
- Stored
|
|
287
|
+
- Per-node platform validation is not exposed separately. `definePipeline` and `toloka check` enforce local contracts; `toloka validate` runs platform spec validation and reports the latest matching Remote Node Check jobs without writing the graph.
|
|
288
|
+
- Stored Remote Node Checks can be represented in settings and the low-level client can list/start them. `toloka check` deliberately runs local source and authored tests; it does not configure or start platform check jobs.
|
|
286
289
|
- Project rename and runtime item-filter authoring have no `toloka` source path. The CLI preserves the live item filter and a live `itemLimit` unless source declares its own limit.
|
|
287
290
|
|
|
288
291
|
- Generation's platform-controlled redo cap, appeal behavior, payment retry, and exact terminal handling are intentionally not configurable SDK settings.
|
|
289
292
|
- Dynamic quorum and per-item overlap are unsupported by both surfaces; this is a platform limitation, not missing TypeScript syntax.
|
|
290
293
|
|
|
291
|
-
For every representable change above, edit the node's source file, run `toloka validate`, inspect `toloka plan`, and apply with `toloka apply`.
|
|
294
|
+
For every representable change above, edit the node's source file, run `toloka validate`, inspect `toloka plan`, and apply with `toloka apply`.
|
|
@@ -26,16 +26,19 @@ 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
|
-
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.
|
|
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`, `config`, 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
|
|
|
40
43
|
## Store credentials once
|
|
41
44
|
|
|
@@ -61,6 +64,8 @@ toloka logout
|
|
|
61
64
|
unless you pass `--show-token`, and exits non-zero when the platform is unreachable — use it to check a
|
|
62
65
|
setup rather than guessing. `logout` deletes the stored file; it does not revoke the key.
|
|
63
66
|
|
|
67
|
+
A globally installed `toloka` auto-updates by default. It checks npm at most once per day and installs a newer `@toloka-platform/sdk` only after that version has been published for at least 24 hours, then re-runs the original command. `config`, `version`, and `help` never trigger a check. Project-local installs and CI skip it. Turn it off with `toloka config set auto-update false`.
|
|
68
|
+
|
|
64
69
|
## Configure the CLI
|
|
65
70
|
|
|
66
71
|
Provide the API key and base URL on networked workflows through `POTATO_API_KEY` and
|
|
@@ -84,7 +89,10 @@ toloka --version
|
|
|
84
89
|
toloka login
|
|
85
90
|
toloka logout
|
|
86
91
|
toloka auth-status
|
|
87
|
-
toloka
|
|
92
|
+
toloka config
|
|
93
|
+
toloka config get [name]
|
|
94
|
+
toloka config set <name> <value>
|
|
95
|
+
toloka project list [--limit <n>]
|
|
88
96
|
toloka project add [alias] [projectId]
|
|
89
97
|
toloka project switch <alias>
|
|
90
98
|
toloka project current
|
|
@@ -93,6 +101,7 @@ toloka project remove <alias>
|
|
|
93
101
|
toloka project create [alias] [--name <name>] [--team-id <id>]
|
|
94
102
|
toloka init
|
|
95
103
|
toloka import [projectId]
|
|
104
|
+
toloka check [pipeline] [--node <name-or-id>] [--json] [--watch]
|
|
96
105
|
toloka validate
|
|
97
106
|
toloka plan
|
|
98
107
|
toloka apply
|
|
@@ -102,7 +111,7 @@ toloka dataset list
|
|
|
102
111
|
toloka dataset fields <name>
|
|
103
112
|
toloka dataset load <name> --out <file> [--limit <n>] [--timeout <ms>]
|
|
104
113
|
toloka dataset upload <name> <file>
|
|
105
|
-
toloka pipeline list
|
|
114
|
+
toloka pipeline list [--limit <n>]
|
|
106
115
|
toloka pipeline run [name] [--wait] [--timeout <ms>]
|
|
107
116
|
toloka pipeline status [name]
|
|
108
117
|
toloka pipeline runs [name] [--limit <n>]
|
|
@@ -122,11 +131,11 @@ toloka skill install <skill-id> [--force]
|
|
|
122
131
|
toloka report <markdown> [--authored-by human|agent] [--confirm]
|
|
123
132
|
```
|
|
124
133
|
|
|
125
|
-
Select the platform project with `toloka project add` or `toloka project create` before `import`, `plan`, or `apply`. `init`
|
|
134
|
+
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.
|
|
126
135
|
|
|
127
136
|
`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.
|
|
128
137
|
|
|
129
|
-
`toloka pipeline cost [name] [--item-limit <n>]` forecasts what a run of a live pipeline would bill. It does not start a run
|
|
138
|
+
`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.
|
|
130
139
|
|
|
131
140
|
## Manage model checkpoints
|
|
132
141
|
|
|
@@ -134,39 +143,44 @@ The global Base Model Catalog is read-only and uses `toloka base-model`. `base-m
|
|
|
134
143
|
|
|
135
144
|
## Adopt an existing live project
|
|
136
145
|
|
|
137
|
-
1.
|
|
138
|
-
2. Run `toloka project add <alias> <projectId
|
|
139
|
-
3. Run `toloka import
|
|
140
|
-
4.
|
|
141
|
-
5.
|
|
142
|
-
6.
|
|
146
|
+
1. Install `@toloka-platform/sdk` in the intended source root.
|
|
147
|
+
2. Run `toloka project add <alias> <projectId>`, or `toloka project switch <alias>` when the project is already registered.
|
|
148
|
+
3. Run `toloka import` into the empty source root.
|
|
149
|
+
4. Run `toloka init` to install or refresh the agent entry points without rebinding project identity.
|
|
150
|
+
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.
|
|
151
|
+
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.
|
|
152
|
+
7. Validate offline, plan, apply, and plan again. Never copy imported IDs to unrelated declarations or invent replacements.
|
|
143
153
|
|
|
144
154
|
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.
|
|
145
155
|
|
|
146
156
|
## Author against a project from scratch
|
|
147
157
|
|
|
148
|
-
1. Run `toloka
|
|
149
|
-
2. Run `toloka project
|
|
158
|
+
1. Run `toloka project create <alias>` for a new project, or `toloka project add <alias> <projectId>` for an existing empty project.
|
|
159
|
+
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.
|
|
150
160
|
3. Create `pipelines/<name>/pipeline.ts` and its imported modules.
|
|
151
161
|
4. Default-export `definePipeline({ dataset, fields, nodes, connections })`.
|
|
152
|
-
5.
|
|
153
|
-
6. Run `toloka
|
|
154
|
-
7.
|
|
155
|
-
8. Run `toloka apply
|
|
162
|
+
5. Add typed node and pipeline-flow tests, then run `toloka check`.
|
|
163
|
+
6. Run `toloka validate`; a not-yet-created pipeline is reported as skipped, not server-validated.
|
|
164
|
+
7. Run `toloka plan` and resolve missing secrets or immutable-field conflicts.
|
|
165
|
+
8. Run `toloka apply`; it reruns `toloka check` before any platform write.
|
|
156
166
|
9. Require a final `toloka plan` to print `No changes.`
|
|
157
167
|
|
|
158
168
|
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.
|
|
159
169
|
|
|
160
|
-
##
|
|
170
|
+
## Check locally and validate against the platform
|
|
161
171
|
|
|
162
|
-
Run the
|
|
172
|
+
Run the complete local gate first, then validate every bound pipeline remotely:
|
|
163
173
|
|
|
164
174
|
```sh
|
|
165
|
-
|
|
175
|
+
toloka check
|
|
166
176
|
toloka validate
|
|
167
177
|
```
|
|
168
178
|
|
|
169
|
-
`toloka
|
|
179
|
+
`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.
|
|
180
|
+
|
|
181
|
+
With `--watch --json`, each completed rerun writes one JSON line. Do not start another watcher while one is active.
|
|
182
|
+
|
|
183
|
+
`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.
|
|
170
184
|
|
|
171
185
|
The authored API still supplies extra local safety:
|
|
172
186
|
|
|
@@ -175,7 +189,7 @@ The authored API still supplies extra local safety:
|
|
|
175
189
|
- It rejects a `connect()` endpoint whose node is absent from `nodes`.
|
|
176
190
|
- Typed ports prevent reversing input and output handles.
|
|
177
191
|
|
|
178
|
-
|
|
192
|
+
`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.
|
|
179
193
|
|
|
180
194
|
## Read and apply a plan
|
|
181
195
|
|
|
@@ -189,6 +203,20 @@ Treat every plan line as evidence of what apply will attempt.
|
|
|
189
203
|
|
|
190
204
|
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.
|
|
191
205
|
|
|
206
|
+
## Test pipeline nodes locally
|
|
207
|
+
|
|
208
|
+
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.
|
|
209
|
+
|
|
210
|
+
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.
|
|
211
|
+
|
|
212
|
+
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.
|
|
213
|
+
|
|
214
|
+
```sh
|
|
215
|
+
toloka check
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
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.
|
|
219
|
+
|
|
192
220
|
## Add dataset rows
|
|
193
221
|
|
|
194
222
|
Declare the fields in source, then upload rows:
|
|
@@ -197,6 +225,8 @@ Declare the fields in source, then upload rows:
|
|
|
197
225
|
toloka dataset upload <dataset> <file>
|
|
198
226
|
```
|
|
199
227
|
|
|
228
|
+
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.
|
|
229
|
+
|
|
200
230
|
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.
|
|
201
231
|
|
|
202
232
|
If a later upload fails with a schema-incompatibility error, inspect the inferred schema from the first successful upload:
|
|
@@ -205,7 +235,7 @@ If a later upload fails with a schema-incompatibility error, inspect the inferre
|
|
|
205
235
|
toloka dataset fields <dataset>
|
|
206
236
|
```
|
|
207
237
|
|
|
208
|
-
That prints `field()` declarations for the top-level inferred columns.
|
|
238
|
+
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.
|
|
209
239
|
|
|
210
240
|
Keep item files outside the source tree or gitignored. Never embed sample or production rows in `pipeline.ts` to make reconciliation create them.
|
|
211
241
|
|
|
@@ -269,9 +299,13 @@ Use the exact name reported by plan. Set it with `toloka secret set`, rerun plan
|
|
|
269
299
|
|
|
270
300
|
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.
|
|
271
301
|
|
|
302
|
+
### Unexplained node drift after apply
|
|
303
|
+
|
|
304
|
+
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.
|
|
305
|
+
|
|
272
306
|
### Server 400 after a clean validate
|
|
273
307
|
|
|
274
|
-
|
|
308
|
+
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.
|
|
275
309
|
|
|
276
310
|
## Use the shipped example
|
|
277
311
|
|
|
@@ -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.8 (`toloka --version`), Bun 1.4.0, macOS 15
|
|
30
30
|
- Platform: https://platform.toloka.ai
|
|
31
31
|
|
|
32
32
|
## Redactions
|
package/src/authoring/index.ts
CHANGED
|
@@ -51,7 +51,7 @@ export interface Connection {
|
|
|
51
51
|
readonly settings: PipelineGraphV1["connections"][number]["settings"];
|
|
52
52
|
}
|
|
53
53
|
|
|
54
|
-
type
|
|
54
|
+
type FieldReference = {
|
|
55
55
|
readonly field: Field;
|
|
56
56
|
readonly optional: boolean;
|
|
57
57
|
};
|
|
@@ -124,26 +124,24 @@ type QuorumOutputEntity = FieldEntity<QuorumNodeSettings["entities"]["output"][n
|
|
|
124
124
|
type QuorumQualityCriterion = FieldTarget<QuorumNodeSettings["qualityCriteria"][number]>;
|
|
125
125
|
type QuorumExam = FieldExam<NonNullable<QuorumNodeSettings["exam"]>>;
|
|
126
126
|
|
|
127
|
-
type MatchPredicate =
|
|
127
|
+
type MatchPredicate = Pick<
|
|
128
128
|
MatchNodeSettings["conditions"][number]["predicates"][number],
|
|
129
|
-
"
|
|
129
|
+
"jsonPath" | "operator" | "value"
|
|
130
130
|
> & {
|
|
131
131
|
readonly field: Field;
|
|
132
132
|
};
|
|
133
133
|
|
|
134
|
-
export type RootAuthoringSettings =
|
|
135
|
-
RootNodeSettings,
|
|
136
|
-
"datasetId" | "itemFilter" | "startedFieldProxies" | "completedFieldProxies"
|
|
137
|
-
> & {
|
|
134
|
+
export type RootAuthoringSettings = Pick<RootNodeSettings, "itemLimit"> & {
|
|
138
135
|
readonly started: ReadonlyArray<RootFieldReference>;
|
|
139
136
|
readonly completed: ReadonlyArray<RootFieldReference>;
|
|
140
137
|
};
|
|
141
138
|
|
|
142
|
-
export type CodeAuthoringSettings =
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
139
|
+
export type CodeAuthoringSettings = Pick<
|
|
140
|
+
CodeNodeSettings,
|
|
141
|
+
"about" | "code" | "envVars" | "checks"
|
|
142
|
+
> & {
|
|
143
|
+
readonly input: ReadonlyArray<FieldReference>;
|
|
144
|
+
readonly output: ReadonlyArray<FieldReference>;
|
|
147
145
|
};
|
|
148
146
|
|
|
149
147
|
export type GenerationAuthoringSettings = Omit<
|
|
@@ -162,15 +160,13 @@ export type GenerationAuthoringSettings = Omit<
|
|
|
162
160
|
readonly exam?: GenerationExam;
|
|
163
161
|
};
|
|
164
162
|
|
|
165
|
-
export type MatchAuthoringSettings =
|
|
163
|
+
export type MatchAuthoringSettings = Pick<MatchNodeSettings, "about"> & {
|
|
166
164
|
readonly conditions: ReadonlyArray<
|
|
167
165
|
Omit<MatchNodeSettings["conditions"][number], "predicates"> & {
|
|
168
166
|
readonly predicates: ReadonlyArray<MatchPredicate>;
|
|
169
167
|
}
|
|
170
168
|
>;
|
|
171
|
-
readonly
|
|
172
|
-
readonly input: ReadonlyArray<FieldProxyReference>;
|
|
173
|
-
};
|
|
169
|
+
readonly input: ReadonlyArray<FieldReference>;
|
|
174
170
|
};
|
|
175
171
|
|
|
176
172
|
export type QuorumAuthoringSettings = Omit<
|
|
@@ -316,11 +312,14 @@ function referencedFields(value: unknown): ReadonlyArray<Field> {
|
|
|
316
312
|
return references;
|
|
317
313
|
}
|
|
318
314
|
|
|
319
|
-
function
|
|
315
|
+
function compileFieldReference(reference: FieldReference): { id: string; optional: boolean } {
|
|
320
316
|
return { id: reference.field.id, optional: reference.optional };
|
|
321
317
|
}
|
|
322
318
|
|
|
323
|
-
function
|
|
319
|
+
function compileRootFieldReference(reference: RootFieldReference): {
|
|
320
|
+
id: string;
|
|
321
|
+
optional: boolean;
|
|
322
|
+
} {
|
|
324
323
|
return isField(reference)
|
|
325
324
|
? { id: reference.id, optional: false }
|
|
326
325
|
: { id: reference.field.id, optional: reference.optional ?? false };
|
|
@@ -457,8 +456,8 @@ function createRoot(
|
|
|
457
456
|
type: "root",
|
|
458
457
|
settings: {
|
|
459
458
|
...settingsWithoutFieldProxies,
|
|
460
|
-
startedFieldProxies: { output: started.map(
|
|
461
|
-
completedFieldProxies: { input: completed.map(
|
|
459
|
+
startedFieldProxies: { output: started.map(compileRootFieldReference) },
|
|
460
|
+
completedFieldProxies: { input: completed.map(compileRootFieldReference) },
|
|
462
461
|
},
|
|
463
462
|
started: outputPort(id, "started"),
|
|
464
463
|
completed: inputPort(id, "completed"),
|
|
@@ -477,14 +476,15 @@ export function code(
|
|
|
477
476
|
options: AuthoringIdOptions = {},
|
|
478
477
|
): AuthoredCodeNode {
|
|
479
478
|
const id = options.id ?? mintedId("code", name);
|
|
479
|
+
const { input, output, ...wireSettings } = settings;
|
|
480
480
|
return {
|
|
481
481
|
id,
|
|
482
482
|
type: "code",
|
|
483
483
|
settings: {
|
|
484
|
-
...
|
|
484
|
+
...wireSettings,
|
|
485
485
|
fieldProxies: {
|
|
486
|
-
input:
|
|
487
|
-
output:
|
|
486
|
+
input: input.map(compileFieldReference),
|
|
487
|
+
output: output.map(compileFieldReference),
|
|
488
488
|
},
|
|
489
489
|
},
|
|
490
490
|
input: inputPort(id, "input"),
|
|
@@ -515,19 +515,20 @@ export function match(
|
|
|
515
515
|
options: AuthoringIdOptions = {},
|
|
516
516
|
): AuthoredMatchNode {
|
|
517
517
|
const id = options.id ?? mintedId("match", name);
|
|
518
|
+
const { conditions, input, ...wireSettings } = settings;
|
|
518
519
|
return {
|
|
519
520
|
id,
|
|
520
521
|
type: "match",
|
|
521
522
|
settings: {
|
|
522
|
-
...
|
|
523
|
-
conditions:
|
|
523
|
+
...wireSettings,
|
|
524
|
+
conditions: conditions.map((condition) => ({
|
|
524
525
|
...condition,
|
|
525
526
|
predicates: condition.predicates.map(({ field: referencedField, ...predicate }) => ({
|
|
526
527
|
...predicate,
|
|
527
528
|
fieldProxyId: referencedField.id,
|
|
528
529
|
})),
|
|
529
530
|
})),
|
|
530
|
-
fieldProxies: { input:
|
|
531
|
+
fieldProxies: { input: input.map(compileFieldReference) },
|
|
531
532
|
},
|
|
532
533
|
input: inputPort(id, "input"),
|
|
533
534
|
default: outputPort(id, "default"),
|
|
@@ -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";
|