@toloka-platform/sdk 0.11.1 → 0.11.2

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.
Binary file
@@ -1 +1 @@
1
- 6a0f8dbdd98f49a9c663bb3051960c4f168961bc4ebd8b428ae75fd5f07ba994 toloka.js.jsc
1
+ 1024b0621937fe114806d81e918925d05e4e71ef8c2f6aa209eabbe9aa102e02 toloka.js.jsc
@@ -1 +1 @@
1
- 20b4b75534617c8aed19bf129f8b78fe18d43a5a91221f1aa4bf2fbc0f792175 toloka.js
1
+ 2e9290a8e04d18db2dd275be6fc273d15459a67f6b1f98d27aebf1c6bdfd7fa1 toloka.js
package/docs/authoring.md CHANGED
@@ -99,6 +99,14 @@ Choose entity cardinality from the task, not from the component's JavaScript val
99
99
 
100
100
  Use `dynamic: true` only when one labeler produces multiple independent entities. Set `maxEntities` to a task-grounded upper bound rather than a generic default. A component may expose an array while still producing one logical entity; for a single upload or recording, keep a scalar field and static entity and select the completed first value with a path such as `$.video_file[0].downloadLink`.
101
101
 
102
+ Entity types and labels are checked locally, for both generation and quorum nodes, before anything is sent to the platform:
103
+
104
+ - `entityType` on every input and output entity must be one of the platform's core entity types. The list ships with the SDK in `src/core-entity-types.json`, generated from the server schema: `JUDGEMENT`, `EXPLANATION`, `PROMPT`, `USER_PROMPT`, `RESPONSE`, `RESPONSE_EVALUATION`, `RUBRIC`, `RUBRICS_GROUP`, `DIALOG`, `DIALOG_STEP`, `DIALOG_EVALUATION`, `SHORT_ANSWER`, `FEEDBACK`, `CONTEXT`, `IMAGE`, `ANNOTATION`, `SINGLE_LABEL`, `MULTI_LABEL`, `VIDEO`, `DOCUMENT`, `GOLDEN_SET`, `LOGS`, `SOLUTION`, `CODE`, `METADATA`, `AUDIO`, `AGENT_TRAJECTORY`, `MCP_SERVER`, `CONTAINER`, `CUSTOM`.
105
+ - An output entity of type `SINGLE_LABEL` or `MULTI_LABEL` needs `allowedLabels` with at least two entries.
106
+ - Every label `value` must be snake_case (`first_video`, not `firstVideo` or `First Video`).
107
+
108
+ A violation is thrown as `PipelineGraphValidationError`, with one issue per offending path, as soon as the pipeline source is loaded — so `toloka check`, `toloka plan`, `toloka apply` and `toloka validate` all fail on it locally before any platform request.
109
+
102
110
  The following task is legitimately dynamic because the labeler can draw one independent box for every visible face:
103
111
 
104
112
  ```ts
@@ -299,7 +307,7 @@ toloka validate
299
307
 
300
308
  `definePipeline` and the typed ports add local declaration and connection invariants. `toloka validate` posts each bound pipeline to `POST /pipelines/:pipelineId/graph/validate` and reports platform spec errors, warnings, and the latest matching check jobs. It does not write the graph.
301
309
 
302
- A pipeline that is not on the platform yet is skipped until `toloka apply` creates it. The published JSON contract is still available as `validate(graph)` for importer and fixture tests; that helper makes no network call.
310
+ A pipeline that is not on the platform yet is skipped until `toloka apply` creates it. The published JSON contract is still available as `validate(graph)` for importer and fixture tests; that helper also runs the local entity checks described under [generation](#generation) and makes no network call.
303
311
 
304
312
  A successful validate is necessary, not sufficient for launch. Gates outside the graph contract — an underpriced audience, a Training course that is enabled but unbuilt, a task UI that was never validated — can still block deployment. See [limitations](./limitations.md).
305
313
 
package/docs/cli.md CHANGED
@@ -56,14 +56,15 @@ If both installations exist, put the desired directory first on `PATH`, run `has
56
56
 
57
57
  ## Configuration
58
58
 
59
- Credentials and the platform base URL resolve from the same precedence order, highest first.
59
+ Credentials and the platform base URL resolve independently. Credentials use this precedence, highest first.
60
60
 
61
61
  | Precedence | Credential source |
62
62
  | ----------- | ------------------------------------------------ |
63
63
  | 1 (highest) | `--api-key` flag |
64
- | 2 | `TOLOKA_API_KEY` environment variable |
65
- | 3 | API key stored by `toloka login --api-key-stdin` |
66
- | 4 (lowest) | browser session stored by `toloka login` |
64
+ | 2 | `TOLOKA_SCOPED_TOKEN` environment variable |
65
+ | 3 | `TOLOKA_API_KEY` environment variable |
66
+ | 4 | API key stored by `toloka login --api-key-stdin` |
67
+ | 5 (lowest) | browser session stored by `toloka login` |
67
68
 
68
69
  | Precedence | Base URL source |
69
70
  | ----------- | --------------------------------------- |
@@ -81,6 +82,8 @@ Credentials and the platform base URL resolve from the same precedence order, hi
81
82
 
82
83
  Earlier releases named these variables `POTATO_API_KEY`, `POTATO_BASE_URL`, and `POTATO_PROJECT_ID`. The CLI still reads each legacy name when its `TOLOKA_*` counterpart is unset, prefers the `TOLOKA_*` value when both are set, and prints `POTATO_API_KEY is deprecated and will stop being read; set TOLOKA_API_KEY instead.` (and the same line for the other two) on stderr once per command. The aliases remain supported for compatibility; rename them to silence the warning and use the current names in new automation.
83
84
 
85
+ `TOLOKA_SCOPED_TOKEN` always sends its value in `X-Scoped-Token`, so it supports both issued `pst_…` tokens and secret placeholders such as `dtn_secret_…`. The `--api-key` and `TOLOKA_API_KEY` slots continue to recognize the `pst_` prefix for backward compatibility. Use the explicit scoped-token variable for new agent and CI configuration.
86
+
84
87
  `toloka login` verifies access against the platform before saving anything. A browser login is saved as a session file under `$TOLOKA_CLI_CREDENTIALS_DIR`, which defaults to `~/.toloka/cli/credentials/<auth-host>/<realm>/<client>.json`. When `TOLOKA_CREDENTIALS_FILE` points at an explicit credentials file, sessions go to a `browser-sessions` directory next to it instead, so that profile stays isolated. The platform choice, a stored API key, and local project aliases live in `$XDG_CONFIG_HOME/toloka/credentials.json`, which defaults to `~/.config/toloka/credentials.json`. Both are written with file mode `0600` in directories with mode `0700`. The legacy `~/.toloka/credentials.json` is still read when the new path has no file. `toloka logout` ends the browser session and deletes both files; it does not revoke an API key.
85
88
 
86
89
  Ordinary usage events (`sdk_cli_command_completed` / `sdk_cli_command_failed`) go to Toloka's PostHog project so we can see which commands run and which error classes they hit. Every failed command with an exception also emits a sanitized PostHog Error Tracking `$exception`; usage errors such as unknown commands or invalid flags do not. Command events contain only an allowlisted command classifier, duration, exit code, outcome or error code, optional HTTP status and server request ID, SDK and Bun versions, actor, and channel. Error Tracking receives the same bounded metadata plus a synthetic exception; raw arguments, flag values, environment variables, API keys, secret names, base URLs and hosts, local paths, project ids, error messages, stack traces, response bodies, and source context are not sent. GeoIP enrichment is disabled. Distinct id starts as a random UUID stored next to the credentials file. After a successful authenticated command, the CLI calls `GET /me` and aliases that UUID onto the platform user id without setting person properties. Set `TOLOKA_TELEMETRY=0` or `DO_NOT_TRACK=1` to disable capture. CI environments are disabled unless `TOLOKA_TELEMETRY=1`. The explicitly approved full-text `report` event is documented in [report](#report).
@@ -190,7 +193,7 @@ By default the command asks whether to sign in through the browser or with an AP
190
193
 
191
194
  Browser login is available for `https://platform.toloka.ai` and the `*.platform.toloka-test.ai` environments. Without a terminal the command opens the chooser page directly and fails fast when no browser can be opened; automation uses an API key instead.
192
195
 
193
- The API key choice does not expire: the key works until it is deleted in the Business Console, which suits scripts, CI, and coding agents. Choosing it asks for the key with masked input; `--api-key-stdin` reads it from standard input instead, so it never appears in shell history or the process list; `--api-key <key>` and `TOLOKA_API_KEY` also work but expose the key to both. Platforms without a known login service go straight to the API key prompt. The command verifies the key and prints `Authenticated against <base-url> with an API key`, then the number of projects visible, where the key is stored, and a reminder that the key does not expire until you delete it in the Business Console.
196
+ The API key choice does not expire: the key works until it is deleted in the Business Console, which suits scripts and CI. Choosing it asks for the key with masked input; `--api-key-stdin` reads it from standard input instead, so it never appears in shell history or the process list; `--api-key <key>` and `TOLOKA_API_KEY` also work but expose the key to both. For an already-issued project-scoped token, set `TOLOKA_SCOPED_TOKEN` for the command instead of storing it with `login`. Platforms without a known login service go straight to the API key prompt. The command verifies the key and prints `Authenticated against <base-url> with an API key`, then the number of projects visible, where the key is stored, and a reminder that the key does not expire until you delete it in the Business Console.
194
197
 
195
198
  ## logout
196
199
 
@@ -210,7 +213,7 @@ Shows which platform and login the CLI would use. Alias: `whoami`.
210
213
  toloka auth-status [--show-token]
211
214
  ```
212
215
 
213
- On success it prints the platform URL with its source, then either the masked API key or a `Login:` line for the browser session and a `Session:` line with the current refresh window, then the selected project, `Reachable: N project(s) visible.`, `Account:` with the email the platform reports, `Internal: true` or `Internal: false` for the account, and a `Teams:` line that marks each team `(internal)` and `(read-only)` where that applies. The project line is printed even when the probe fails. `--show-token` prints the full API key or the browser session's live access token instead of the mask; pass it only when you need the raw value on this terminal. It exits `1` when no login is in effect, when the browser session has expired, when the platform rejects the login (`Rejected: …`, followed by the advice to run `toloka login`), when the platform does not answer (`Not reachable: …`; the login was not evaluated), or when the platform fails (`Platform error: …`).
216
+ On success it prints the platform URL with its source, then either the masked API key, the masked scoped token with `from TOLOKA_SCOPED_TOKEN`, or a `Login:` line for the browser session and a `Session:` line with the current refresh window. It then prints the selected project, `Reachable: N project(s) visible.`, `Account:` with the email the platform reports, `Internal: true` or `Internal: false` for the account, and a `Teams:` line that marks each team `(internal)` and `(read-only)` where that applies. The project line is printed even when the probe fails. `--show-token` prints the full active credential instead of the mask; pass it only when you need the raw value on this terminal. It exits `1` when no login is in effect, when the browser session has expired, when the platform rejects the credential, or when the platform cannot be reached.
214
217
 
215
218
  ## config
216
219
 
@@ -415,6 +418,16 @@ In a terminal the command asks `Send this report to Toloka? [y/N]`; outside a te
415
418
 
416
419
  One `sdk_cli_issue_report_submitted` event carries `report_id`, the complete `report_text`, its UTF-8 `report_length_bytes`, `authored_by`, and `platform`, plus the standard pseudonymous CLI telemetry context. The command never reads or sends an API key. It flushes PostHog before printing `Submitted issue report: <id>`; a delivery failure exits non-zero and prints no success ID. When CLI telemetry is disabled, issue reporting is unavailable rather than bypassing that preference.
417
420
 
421
+ ## ui
422
+
423
+ Inspect the Task UI components the launcher renders. The command opens the launcher in a headless Chromium (Playwright from the project's `node_modules`), so it needs the same local setup as `toloka check` and no credentials.
424
+
425
+ ```sh
426
+ toloka ui components [names...] [--json]
427
+ ```
428
+
429
+ Without names it prints one line per public component (`name: description`). With names it prints each component's description, when to use it, capabilities, limitations, best practices, the `propsType` contract, the value shape, and the documented JSX example with its output. An unknown name fails with `Unknown Task UI components: <name>`. `--json` prints the same contracts keyed by component name. The output always reflects the currently deployed launcher, so run it instead of guessing component names or props from memory.
430
+
418
431
  ## skill
419
432
 
420
433
  Discover bundled agent skills and install them into the project. The Skill Catalog ships inside the SDK package, so these actions are local and deterministic: they need no credentials, make no network requests, and change with the package version only.
@@ -37,7 +37,7 @@ The session renews itself for up to 14 days; after that, run `toloka login` agai
37
37
  toloka whoami
38
38
  ```
39
39
 
40
- CI does not need `login`. Set `TOLOKA_API_KEY` and the stored login is ignored; an agent without a terminal can pipe a key to `toloka login --api-key-stdin`. See the precedence table in the [CLI reference](./cli.md).
40
+ CI does not need `login`. Set `TOLOKA_API_KEY` for a long-lived API key or `TOLOKA_SCOPED_TOKEN` for an issued project-scoped token, and the stored login is ignored; an agent without a terminal can pipe a long-lived key to `toloka login --api-key-stdin`. See the precedence table in the [CLI reference](./cli.md).
41
41
 
42
42
  ## Start with an agent
43
43
 
package/docs/testing.md CHANGED
@@ -83,7 +83,7 @@ Use an ordinary TypeScript loop to register several `test.code` or `test.ui` sce
83
83
 
84
84
  The low-level `ui` fixture remains available inside ordinary Vitest tests for component discovery and exploratory work, but plain tests do not satisfy required node coverage. Use typed wrappers for committed gate evidence. Importing `@toloka-platform/sdk/testing` from a plain Bun script executes Vitest fixture setup and fails.
85
85
 
86
- - `ui.componentDocumentation(names?)` returns the current launcher's supported component contracts. Call it before choosing component names or props.
86
+ - `ui.componentDocumentation(names?)` returns the current launcher's supported component contracts. `ui` is a fixture argument of `test` (`test("…", async ({ ui }) => …)`), not an export. Outside a test, `toloka ui components [names...]` prints the same contracts.
87
87
  - `ui.render(pipeline, node, options)` creates a fresh browser context and returns a Task UI session for exactly that authored node. `test.ui` supplies the equivalent bound `render(options?)` helper.
88
88
 
89
89
  The returned Task UI session supports:
@@ -96,7 +96,7 @@ toloka pipeline results <runId> --failed
96
96
  Not logged in. Run toloka login once to sign in through your browser on this machine:
97
97
  ```
98
98
 
99
- Run `toloka login`. Where a browser is not available, pipe an API key to `toloka login --api-key-stdin` or set `TOLOKA_API_KEY`. `toloka whoami` reports which platform and which login source is in effect, and exits non-zero when the platform is unreachable.
99
+ Run `toloka login`. Where a browser is not available, pipe a long-lived API key to `toloka login --api-key-stdin` or set `TOLOKA_API_KEY`; set `TOLOKA_SCOPED_TOKEN` when using an issued project-scoped token. `toloka whoami` reports which platform and which credential source is in effect, and exits non-zero when the platform is unreachable.
100
100
 
101
101
  ## The browser login expired
102
102
 
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@toloka-platform/sdk",
3
- "version": "0.11.1",
3
+ "version": "0.11.2",
4
4
  "private": false,
5
5
  "description": "Build, inspect, and operate Toloka pipeline projects as TypeScript source. The public API may change between minor versions before 1.0.",
6
6
  "license": "Apache-2.0",
@@ -77,6 +77,6 @@
77
77
  "bun": ">=1.4.0"
78
78
  },
79
79
  "tolokaBuild": {
80
- "commit": "62d9c4558758f7409b65de7c849c8a2e8d15ba9a"
80
+ "commit": "33545324d935ee7d0e9da6f845c03b25ae49759f"
81
81
  }
82
82
  }
@@ -11,8 +11,8 @@
11
11
  "testing",
12
12
  "checks"
13
13
  ],
14
- "version": "0.11.1",
15
- "digest": "79283e8ca89ddafa3d6206be108db32046f6941fb52d4b622aec72c20b791de7",
14
+ "version": "0.11.2",
15
+ "digest": "f97f94dd6eff745884e07d9f19cc414fdc2fe5d9b7992e062e7beb0058dcce59",
16
16
  "files": [
17
17
  "SKILL.md",
18
18
  "references/node-semantics.md",
@@ -29,7 +29,7 @@
29
29
  "bug",
30
30
  "report"
31
31
  ],
32
- "version": "0.11.1",
32
+ "version": "0.11.2",
33
33
  "digest": "70f0bd2c80b32f87896aeb27c363b8a2d37f40e22424f57fe1aad142e125ad67",
34
34
  "files": [
35
35
  "SKILL.md",
@@ -46,7 +46,7 @@
46
46
  "getting-started",
47
47
  "agent"
48
48
  ],
49
- "version": "0.11.1",
49
+ "version": "0.11.2",
50
50
  "digest": "ccf148c682de4e452bb031db95d3933257899006b1766f0fdbc3d15265a63bb9",
51
51
  "files": [
52
52
  "SKILL.md"
@@ -63,7 +63,7 @@
63
63
  "sft",
64
64
  "models"
65
65
  ],
66
- "version": "0.11.1",
66
+ "version": "0.11.2",
67
67
  "digest": "279c69977eef170dada20b664ee0c30858681e84a2cabb2fdff18b8bb1bdc055",
68
68
  "files": [
69
69
  "SKILL.md"
@@ -80,7 +80,7 @@
80
80
  "prompt",
81
81
  "models"
82
82
  ],
83
- "version": "0.11.1",
83
+ "version": "0.11.2",
84
84
  "digest": "b9f314eb6b39754bc8d2ae79aca3dc680fced5f14948c975d1e084a83c2e0a8c",
85
85
  "files": [
86
86
  "SKILL.md"
@@ -80,6 +80,7 @@ Distinguish server requirements from optional design choices. The following cons
80
80
  - **Training is part of the node, not extra.** A quorum node requires `onboarding`, and while it is enabled the platform wants at least three steps and at least one quiz block. Preserve an explicit `enabled: false` that a user or `toloka import` put there; never switch Training on to satisfy your own sense of completeness.
81
81
  - **Optional settings should serve the task.** Exams, non-default QA, audience reuse, and smart search can be useful when the requirements call for them. Omit blocks that add no value rather than copying placeholders; an empty disabled block is still authored state. Preserve imported choices unless changing them is part of the task, and explain additions that affect cost or expert eligibility.
82
82
  - **Run scope belongs to the platform.** `itemFilter` and `itemLimit` on the root node are set from the platform, and apply preserves whatever is live unless source declares its own value. Do not add an item limit to source to "restore" one you saw in the UI.
83
+ - **Entities use a closed vocabulary, and label entities need choices.** `entityType` must be one of the platform core types listed in [node semantics](references/node-semantics.md): `CONTEXT` for any non-media input the expert reads (text, numbers, objects), `IMAGE`/`VIDEO`/`AUDIO`/`DOCUMENT` for media inputs, `SINGLE_LABEL` or `MULTI_LABEL` for fixed choices, `SHORT_ANSWER` or `RESPONSE` for produced text, `EXPLANATION` only for reasoning about another value. Never invent `TEXT`, `LABEL`, or another name. Every `SINGLE_LABEL`/`MULTI_LABEL` output needs `allowedLabels` with at least two `{ label, value }` entries whose `value` is snake_case. The SDK checks these when it loads the source, so `toloka check` (and `toloka plan`, `toloka apply`, `toloka validate`) fails locally with `PipelineGraphValidationError` before any platform request; read the reference before writing a node instead of discovering them from a failed check.
83
84
 
84
85
  ## Know the validation boundary
85
86
 
@@ -89,7 +90,7 @@ Distinguish server requirements from optional design choices. The following cons
89
90
 
90
91
  `toloka validate` posts each bound graph to the platform. Treat source/spec errors as blocking and warnings as informational. A `not_ran_node_check` concerns a separate Remote Node Check and does not replace a passing Local Check Gate, but its remote-validation severity still matters: lint checks report it as a blocking error while other checks can report it as a warning. A pipeline not yet on the platform is skipped until `toloka apply` creates it. When a reviewed plan adds a dataset field already bound by an existing live pipeline, pre-apply validation can report that exact field as missing; apply only the reviewed additive plan, then require post-apply validation to clear source/spec errors. Never rewrite valid source for a missing Remote Node Check.
91
92
 
92
- Before choosing Task UI components, call `ui.componentDocumentation()` from a local `@toloka-platform/sdk/testing` scenario. A component's usable contract is the returned `propsType` plus its returned `valueType` and `example`; shared `Field` props such as `path`, `label`, `hint`, and `validation` can be confirmed from any current `Field.*` example, but do not invent any other prop. For structured or array-valued Fields, derive the dataset field schema from `valueType`, then feed the documented example value through `setValue`, `getValue`, and `submit` in the UI scenario so upstream contract drift fails locally. Never translate undocumented property names or replace missing values with defaults. Every identifier in `ui.code` must be a returned component or documented `actions` API, and JSX must have one root element. Register each required UI scenario with `test.ui(pipeline, node, name, callback)` and submit through its bound `render` helper. Run `toloka check <pipeline> --node <name-or-id>` after every Task UI edit; require passing UI types, empty browser validation errors, valid postprocessed entity output, and visual inspection where relevant. Never weaken authored validation merely to make a test pass.
93
+ Before choosing Task UI components, list them from the launcher with `toloka ui components` (one line per component) and read the contract of the ones you intend to use with `toloka ui components <name> [name...]`; never guess component names or props from memory. If this SDK version has no `ui` command, the same data comes from the `ui` fixture of the `test` exported by `@toloka-platform/sdk/testing` (`ui` is a fixture argument, not an export): write a throwaway `probe.spec.ts` with `test("components", async ({ ui }) => { console.log(JSON.stringify(await ui.componentDocumentation(["View.Image"]), null, 2)); });`, run `bunx vitest run probe.spec.ts`, and delete the file before `toloka check`. A component's usable contract is the returned `propsType` plus its returned `valueType` and `example`; shared `Field` props such as `path`, `label`, `hint`, and `validation` can be confirmed from any current `Field.*` example, but do not invent any other prop. For structured or array-valued Fields, derive the dataset field schema from `valueType`, then feed the documented example value through `setValue`, `getValue`, and `submit` in the UI scenario so upstream contract drift fails locally. Never translate undocumented property names or replace missing values with defaults. Every identifier in `ui.code` must be a returned component or documented `actions` API, and JSX must have one root element. Register each required UI scenario with `test.ui(pipeline, node, name, callback)` and submit through its bound `render` helper. Run `toloka check <pipeline> --node <name-or-id>` after every Task UI edit; require passing UI types, empty browser validation errors, valid postprocessed entity output, and visual inspection where relevant. Never weaken authored validation merely to make a test pass.
93
94
 
94
95
  `ui.code` accepts TSX, including TypeScript annotations. `preprocess` and `postprocess` are JavaScript function bodies; do not put TypeScript-only syntax in either transform.
95
96
 
@@ -104,8 +105,9 @@ Register code behavior with `test.code(pipeline, node, name, callback)` and exec
104
105
 
105
106
  `toloka login` signs the user in through the browser, or stores an API key when they choose that, and
106
107
  keeps the login with owner-only permissions, so no later command needs `--api-key`. Precedence is flag,
107
- then environment variable (`TOLOKA_API_KEY`, `TOLOKA_BASE_URL`), then the stored API key, then the
108
- browser session. `toloka auth-status` reports what is in effect and where it came from; `toloka logout`
108
+ then environment variable (`TOLOKA_SCOPED_TOKEN`, then `TOLOKA_API_KEY`; `TOLOKA_BASE_URL`), then the
109
+ stored API key, then the browser session. `TOLOKA_SCOPED_TOKEN` is a project-scoped token minted for the
110
+ user by the platform (for example inside an agent sandbox); it never grants more than that user has. `toloka auth-status` reports what is in effect and where it came from; `toloka logout`
109
111
  ends the browser session and removes the stored files. If credentials are missing, run `toloka login --browser`
110
112
  yourself: it opens the sign-in page in the user's browser and waits, so ask the user to pick their
111
113
  provider and finish signing in there. Never request a key in chat.
@@ -142,11 +142,11 @@ An authored entity references a declared `field`. Choose its cardinality from th
142
142
  - One logical result from one labeler is static. A caption, verdict, rating, recording, or upload remains one entity even when its value is an object or a component holds it in an array. Omit `dynamic` and `maxEntities`, and use a `jsonPath` without `[*]`.
143
143
  - Multiple independent results from one labeler are dynamic. Examples include one box per visible face and one defect record per discovered defect. Set `dynamic: true`, use exactly one `[*]` in `jsonPath`, and set `maxEntities` to the tightest upper bound justified by the task. Never use 50 as an unexamined default.
144
144
  - Do not choose `dynamic` from a TypeScript or JSON container shape. Ask whether the task expects one logical result or a repeated collection; the answer owns the entity model.
145
- - Use `allowedLabels: [{ label, value }]` for fixed choices. Use `SINGLE_LABEL` for exactly one stored value and `MULTI_LABEL` for multiple values.
145
+ - Use `allowedLabels: [{ label, value }]` for fixed choices. Use `SINGLE_LABEL` for exactly one stored value and `MULTI_LABEL` for multiple values. Every `SINGLE_LABEL`/`MULTI_LABEL` output needs at least two entries, and each `value` must be snake_case.
146
146
  - 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
147
  - 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
148
 
149
- `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
+ `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. The full vocabulary, generated from the server schema into the SDK's `core-entity-types.json`, is `JUDGEMENT`, `EXPLANATION`, `PROMPT`, `USER_PROMPT`, `RESPONSE`, `RESPONSE_EVALUATION`, `RUBRIC`, `RUBRICS_GROUP`, `DIALOG`, `DIALOG_STEP`, `DIALOG_EVALUATION`, `SHORT_ANSWER`, `FEEDBACK`, `CONTEXT`, `IMAGE`, `ANNOTATION`, `SINGLE_LABEL`, `MULTI_LABEL`, `VIDEO`, `DOCUMENT`, `GOLDEN_SET`, `LOGS`, `SOLUTION`, `CODE`, `METADATA`, `AUDIO`, `AGENT_TRAJECTORY`, `MCP_SERVER`, `CONTAINER`, `CUSTOM`. The SDK validates `entityType` and the `allowedLabels` rules when it loads the source, so `toloka check` fails locally with `PipelineGraphValidationError` before anything is sent to the platform.
150
150
 
151
151
  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.
152
152
 
@@ -73,8 +73,10 @@ The API key choice does not expire: the key works until it is deleted in the Bus
73
73
  suits scripts, CI, and coding agents. Type it at the masked prompt, or pipe it to `toloka login --api-key-stdin`.
74
74
  `toloka login --api-key <key>` also works but leaves the key in shell history. Most later commands read the stored login, so
75
75
  `--api-key` becomes optional. For networked commands that do not mutate the machine project registry,
76
- credential precedence is flag, then `TOLOKA_API_KEY`, then the stored API key, then the browser
77
- session; CI can use an environment variable without running login. The base URL defaults to the platform.
76
+ credential precedence is flag, then `TOLOKA_SCOPED_TOKEN`, then `TOLOKA_API_KEY`, then the stored API key,
77
+ then the browser session; CI can use an environment variable without running login. A scoped token is
78
+ minted by the platform for one user and one project (agent sandboxes receive it in `TOLOKA_SCOPED_TOKEN`);
79
+ it cannot issue further tokens and expires on its own. The base URL defaults to the platform.
78
80
 
79
81
  ```sh
80
82
  toloka auth-status # or toloka whoami
@@ -100,8 +102,8 @@ A scoped token downloads project files in two steps: a request to `ProjectFile.u
100
102
 
101
103
  ## Configure the CLI
102
104
 
103
- Provide the API key and base URL on networked workflows through `TOLOKA_API_KEY` and
104
- `TOLOKA_BASE_URL`, or with `--api-key` and `--base-url`. A base URL with a path (a gateway such as
105
+ Provide the credential and base URL on networked workflows through `TOLOKA_SCOPED_TOKEN` or
106
+ `TOLOKA_API_KEY` and `TOLOKA_BASE_URL`, or with `--api-key` and `--base-url`. A base URL with a path (a gateway such as
105
107
  `https://gateway.example/ss/pp.1`) keeps that path on every request, with or without a trailing
106
108
  slash; use the given URL unchanged. The registry-mutating commands
107
109
  `toloka project add`, `toloka project switch`, `toloka project remove`, and `toloka project create`
@@ -273,7 +275,7 @@ Apply preserves existing live layout and the root's live item filter. Do not add
273
275
 
274
276
  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.
275
277
 
276
- 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.
278
+ Before choosing component names or props, run `toloka ui components` to list the launcher's components and `toloka ui components <name> [name...]` for their contracts (on an SDK without the `ui` command, use the `ui` fixture argument of `test` from `@toloka-platform/sdk/testing` in a throwaway spec and delete it afterwards). 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.
277
279
 
278
280
  Register representative connected flows with `test.pipeline(pipeline, name, callback)`. Choose fixtures from available data that exercise the task, redacting sensitive values while preserving field names, nesting, optionality, and media/value shapes. Use synthetic cases for privacy or boundaries absent from the sample. Use the callback's bound `runCode` and `renderUi` helpers, merge only declared node outputs, and assert the final `root.completed` fields. Cover meaningful match branches, optional routes, and failures as well as the happy path.
279
281
 
package/src/cli/auth.ts CHANGED
@@ -451,27 +451,47 @@ export type AuthStatusOptions = {
451
451
  export type AuthStatusResult = { readonly authenticated: boolean; readonly output: string };
452
452
 
453
453
  type ResolvedValue = { readonly value: string; readonly source: string };
454
+ type ResolvedCredential = ResolvedValue & {
455
+ readonly type: "apiKey" | "scopedToken";
456
+ readonly environmentVariable?: "TOLOKA_SCOPED_TOKEN" | "TOLOKA_API_KEY";
457
+ };
454
458
 
455
- const firstDefined = (
456
- candidates: ReadonlyArray<ResolvedValue | undefined>,
457
- ): ResolvedValue | undefined => candidates.find((candidate) => candidate !== undefined);
459
+ const firstDefined = <T>(candidates: ReadonlyArray<T | undefined>): T | undefined =>
460
+ candidates.find((candidate) => candidate !== undefined);
458
461
 
459
- const resolveStatusApiKey = (
462
+ const resolveStatusCredential = (
460
463
  options: AuthStatusOptions,
461
464
  environment: CliEnvironment,
462
465
  found: FoundCredentials | undefined,
463
466
  storedPath: string,
464
- ): ResolvedValue | undefined =>
465
- firstDefined([
467
+ ): ResolvedCredential | undefined =>
468
+ firstDefined<ResolvedCredential>([
466
469
  options.flagApiKey === undefined
467
470
  ? undefined
468
- : { value: options.flagApiKey, source: "from --api-key" },
471
+ : { type: "apiKey", value: options.flagApiKey, source: "from --api-key" },
472
+ environment.TOLOKA_SCOPED_TOKEN === undefined
473
+ ? undefined
474
+ : {
475
+ type: "scopedToken",
476
+ value: environment.TOLOKA_SCOPED_TOKEN,
477
+ source: "from TOLOKA_SCOPED_TOKEN",
478
+ environmentVariable: "TOLOKA_SCOPED_TOKEN",
479
+ },
469
480
  environment.TOLOKA_API_KEY === undefined
470
481
  ? undefined
471
- : { value: environment.TOLOKA_API_KEY, source: "from TOLOKA_API_KEY" },
482
+ : {
483
+ type: "apiKey",
484
+ value: environment.TOLOKA_API_KEY,
485
+ source: "from TOLOKA_API_KEY",
486
+ environmentVariable: "TOLOKA_API_KEY",
487
+ },
472
488
  found?.credentials.apiKey === undefined
473
489
  ? undefined
474
- : { value: found.credentials.apiKey, source: `stored in ${storedPath}` },
490
+ : {
491
+ type: "apiKey",
492
+ value: found.credentials.apiKey,
493
+ source: `stored in ${storedPath}`,
494
+ },
475
495
  ]);
476
496
 
477
497
  const resolvePlatform = (
@@ -576,17 +596,23 @@ export async function authStatus(options: AuthStatusOptions = {}): Promise<AuthS
576
596
  const now = (options.now ?? unixSeconds)();
577
597
  const credentialLookup =
578
598
  options.credentialLookup ??
579
- (await findCommandCredentials(environment, options.flagApiKey ?? environment.TOLOKA_API_KEY));
599
+ (await findCommandCredentials(
600
+ environment,
601
+ options.flagApiKey ??
602
+ environment.TOLOKA_SCOPED_TOKEN ??
603
+ environment.TOLOKA_API_KEY,
604
+ ));
580
605
  const { found, warning } = credentialLookup;
581
606
  const storedPath = found?.path ?? credentialsPath(environment);
582
- const apiKey = resolveStatusApiKey(options, environment, found, storedPath);
607
+ const credential = resolveStatusCredential(options, environment, found, storedPath);
583
608
  const platform = resolvePlatform(options, environment, found);
584
- const browser = apiKey === undefined ? await findBrowserLogin(platform.value, environment) : undefined;
609
+ const browser =
610
+ credential === undefined ? await findBrowserLogin(platform.value, environment) : undefined;
585
611
  const projectLine = formatProjectContextLine(options.project, found?.credentials);
586
612
 
587
613
  const lines: string[] = warning === undefined ? [] : [warning, ""];
588
614
 
589
- if (apiKey === undefined && browser === undefined) {
615
+ if (credential === undefined && browser === undefined) {
590
616
  lines.push(
591
617
  "Not logged in.",
592
618
  "",
@@ -600,11 +626,15 @@ export async function authStatus(options: AuthStatusOptions = {}): Promise<AuthS
600
626
  const { value: baseUrl, source: urlSource } = platform;
601
627
  lines.push(`Platform: ${baseUrl} (${urlSource})`);
602
628
  let clientConfig: ClientConfig;
603
- if (apiKey !== undefined) {
629
+ if (credential !== undefined) {
630
+ const label = credential.type === "scopedToken" ? "Scoped token:" : "API key: ";
604
631
  lines.push(
605
- `API key: ${options.showToken === true ? apiKey.value : maskApiKey(apiKey.value)} (${apiKey.source})`,
632
+ `${label} ${options.showToken === true ? credential.value : maskApiKey(credential.value)} (${credential.source})`,
606
633
  );
607
- clientConfig = { baseUrl, apiKey: apiKey.value };
634
+ clientConfig =
635
+ credential.type === "scopedToken"
636
+ ? { baseUrl, scopedToken: credential.value }
637
+ : { baseUrl, apiKey: credential.value };
608
638
  } else {
609
639
  assertCli(browser, "No login is in effect.", { hint: { description: "Run toloka login." } });
610
640
  const status = await browserStatus(browser, baseUrl, environment, options, now);
@@ -617,8 +647,11 @@ export async function authStatus(options: AuthStatusOptions = {}): Promise<AuthS
617
647
  }
618
648
  lines.push(projectLine);
619
649
 
620
- if (environment.TOLOKA_API_KEY !== undefined && found?.credentials.apiKey !== undefined) {
621
- lines.push("", `TOLOKA_API_KEY is set, so the key stored in ${found.path} is ignored.`);
650
+ if (found?.credentials.apiKey !== undefined && credential?.environmentVariable !== undefined) {
651
+ lines.push(
652
+ "",
653
+ `${credential.environmentVariable} is set, so the key stored in ${found.path} is ignored.`,
654
+ );
622
655
  }
623
656
  if (found?.legacy === true) {
624
657
  lines.push(
package/src/cli/check.ts CHANGED
@@ -25,7 +25,7 @@ import {
25
25
  } from "../testing/test-evidence";
26
26
  import { cliRelaunchCommand } from "./cli-entry";
27
27
  import { bunSubprocessEnvironment } from "./native-runtime";
28
- import { API_KEY_ENVIRONMENT_NAMES } from "./environment";
28
+ import { CREDENTIAL_ENVIRONMENT_NAMES } from "./environment";
29
29
  import { loadProjectSource, type ProjectSource } from "./source";
30
30
  import { assertCli, REPORT_HINT, TolokaCliError } from "./cli-error";
31
31
  import type { TolokaCliErrorOptions } from "./cli-error";
@@ -276,13 +276,13 @@ const loadSourceWithoutPlatformCredentials = async (
276
276
  environment: NodeJS.ProcessEnv,
277
277
  ): Promise<ProjectSource> => {
278
278
  const targets = [process.env, environment];
279
- const saved = targets.map((current) => API_KEY_ENVIRONMENT_NAMES.map((name) => current[name]));
280
- for (const current of targets) for (const name of API_KEY_ENVIRONMENT_NAMES) delete current[name];
279
+ const saved = targets.map((current) => CREDENTIAL_ENVIRONMENT_NAMES.map((name) => current[name]));
280
+ for (const current of targets) for (const name of CREDENTIAL_ENVIRONMENT_NAMES) delete current[name];
281
281
  try {
282
282
  return await loadProjectSource(sourceRoot);
283
283
  } finally {
284
284
  targets.forEach((current, target) => {
285
- API_KEY_ENVIRONMENT_NAMES.forEach((name, index) => {
285
+ CREDENTIAL_ENVIRONMENT_NAMES.forEach((name, index) => {
286
286
  const value = saved[target]?.[index];
287
287
  if (value === undefined) delete current[name];
288
288
  else current[name] = value;
@@ -693,7 +693,7 @@ export async function checkProject(
693
693
  try {
694
694
  const selectedPipeline = selected.pipelines.length === 1 ? selected.pipelines[0] : undefined;
695
695
  const checkEnvironment = { ...(options.environment ?? process.env) };
696
- for (const name of API_KEY_ENVIRONMENT_NAMES) delete checkEnvironment[name];
696
+ for (const name of CREDENTIAL_ENVIRONMENT_NAMES) delete checkEnvironment[name];
697
697
  const environment = {
698
698
  ...checkEnvironment,
699
699
  [TEST_EVIDENCE_DIR_ENV]: evidenceDirectory,
@@ -59,13 +59,15 @@ export const registerAuthCommands = (program: Command, host: CommandHost): void
59
59
  program
60
60
  .command("auth-status")
61
61
  .alias("whoami")
62
- .description("show which platform and login the CLI would use")
63
- .option("--show-token", "print the API key or access token instead of masking it", false)
62
+ .description("show which platform and credential the CLI would use")
63
+ .option("--show-token", "print the API key, scoped token, or access token instead of masking it", false)
64
64
  .action(async (options: { showToken: boolean }) => {
65
65
  const flags = globals();
66
66
  const credentialLookup = await findCommandCredentials(
67
67
  context.environment,
68
- flags.apiKey ?? context.environment.TOLOKA_API_KEY,
68
+ flags.apiKey ??
69
+ context.environment.TOLOKA_SCOPED_TOKEN ??
70
+ context.environment.TOLOKA_API_KEY,
69
71
  );
70
72
  const stored = credentialLookup.found?.credentials;
71
73
  const sourceRoot = await resolveSourceRoot(flags, context.io);
@@ -15,6 +15,7 @@ import { readTolokaState } from "../../state";
15
15
  import { assertCli } from "../cli-error";
16
16
  import { formatTable } from "../table";
17
17
  import type { CreateScopedTokenBodyAnyOfPreset, ScopedTokenSummary } from "../../../openapi/gen/models";
18
+ import { SCOPED_TOKEN_PREFIX } from "../../transport";
18
19
 
19
20
  const TOKEN_PRESETS: readonly CreateScopedTokenBodyAnyOfPreset[] = [
20
21
  "read",
@@ -157,8 +158,12 @@ export const registerProjectCommands = (program: Command, host: CommandHost): vo
157
158
  expiresIn?: number;
158
159
  projects?: string;
159
160
  }) => {
160
- const { client, projectId } = await resolveContext(globals(), context);
161
+ const { client, credential, projectId } = await resolveContext(globals(), context);
161
162
  assertProjectSelected(projectId);
163
+ const usesScopedToken =
164
+ credential.type === "scopedToken" ||
165
+ (credential.type === "apiKey" && credential.apiKey.startsWith(SCOPED_TOKEN_PREFIX));
166
+ assertCli(!usesScopedToken, "A scoped token cannot issue tokens.", { hint: { disabled: true } });
162
167
  const scope =
163
168
  options.permissions === undefined
164
169
  ? { preset: options.preset }
@@ -174,7 +179,7 @@ export const registerProjectCommands = (program: Command, host: CommandHost): vo
174
179
  : { projectIds: options.projects.split(",").map((projectId) => projectId.trim()) }),
175
180
  });
176
181
  context.io.stdout(token);
177
- context.io.stderr(`Token expires at ${expiresAt}. Use it as TOLOKA_API_KEY or --api-key.`);
182
+ context.io.stderr(`Token expires at ${expiresAt}. Use it as TOLOKA_SCOPED_TOKEN.`);
178
183
  },
179
184
  );
180
185
  project
@@ -0,0 +1,23 @@
1
+ import type { Command } from "commander";
2
+ import { describeTaskUiComponents } from "../../testing/task-ui-browser";
3
+ import { resolveSourceRoot, type CommandHost } from "../context";
4
+ import { uiComponentsCommand } from "../ui";
5
+
6
+ export const registerUiCommands = (program: Command, host: CommandHost): void => {
7
+ const { context, globals } = host;
8
+ const ui = program.command("ui").description("inspect the Task UI components the launcher renders");
9
+ ui.command("components")
10
+ .argument("[names...]", "component names such as Field.RadioGroup; omit to list every component")
11
+ .option("--json", "print the component contracts as JSON", false)
12
+ .description("list Task UI components, or print the props, value shape, and example of the named ones")
13
+ .action(async (names: string[], options: { json: boolean }) => {
14
+ const sourceRoot = await resolveSourceRoot(globals(), context.io);
15
+ await uiComponentsCommand({
16
+ sourceRoot,
17
+ names,
18
+ json: options.json,
19
+ io: context.io,
20
+ describe: context.taskUiComponents ?? describeTaskUiComponents,
21
+ });
22
+ });
23
+ };
package/src/cli/config.ts CHANGED
@@ -12,12 +12,14 @@ export type TolokaCliFlags = {
12
12
 
13
13
  export type TolokaEnvironment = Readonly<Record<string, string | undefined>> & {
14
14
  readonly TOLOKA_API_KEY?: string;
15
+ readonly TOLOKA_SCOPED_TOKEN?: string;
15
16
  readonly TOLOKA_BASE_URL?: string;
16
17
  readonly TOLOKA_PROJECT_ID?: string;
17
18
  };
18
19
 
19
20
  export type CliCredential =
20
21
  | { readonly type: "apiKey"; readonly apiKey: string }
22
+ | { readonly type: "scopedToken"; readonly scopedToken: string }
21
23
  | { readonly type: "browser"; readonly provider: BrowserLoginProvider };
22
24
 
23
25
  export type ResolvedTolokaConfig = {
@@ -60,15 +62,22 @@ export const MISSING_CREDENTIALS_MESSAGE = [
60
62
  "",
61
63
  " toloka login",
62
64
  "",
63
- "Automation can pass --api-key, set TOLOKA_API_KEY, or pipe a key to toloka login --api-key-stdin.",
65
+ "Automation can pass --api-key, set TOLOKA_SCOPED_TOKEN or TOLOKA_API_KEY, or pipe a key to toloka login --api-key-stdin.",
64
66
  "Run toloka auth-status to see what is in effect.",
65
67
  ].join("\n");
66
68
 
67
- export const resolveApiKey = (
69
+ export const resolveConfiguredCredential = (
68
70
  flags: TolokaCliFlags,
69
71
  environment: TolokaEnvironment,
70
72
  stored?: StoredCredentials,
71
- ): string | undefined => flags.apiKey ?? environment.TOLOKA_API_KEY ?? stored?.apiKey;
73
+ ): Exclude<CliCredential, { readonly type: "browser" }> | undefined => {
74
+ if (flags.apiKey !== undefined) return { type: "apiKey", apiKey: flags.apiKey };
75
+ if (environment.TOLOKA_SCOPED_TOKEN !== undefined) {
76
+ return { type: "scopedToken", scopedToken: environment.TOLOKA_SCOPED_TOKEN };
77
+ }
78
+ const apiKey = environment.TOLOKA_API_KEY ?? stored?.apiKey;
79
+ return apiKey === undefined ? undefined : { type: "apiKey", apiKey };
80
+ };
72
81
 
73
82
  export const resolveBaseUrl = (
74
83
  flags: TolokaCliFlags,
@@ -88,7 +97,7 @@ export function resolveTolokaConfig(
88
97
  stored?: StoredCredentials,
89
98
  browserLogin?: BrowserLoginProvider,
90
99
  ): ResolvedTolokaConfig {
91
- const apiKey = resolveApiKey(flags, environment, stored);
100
+ const configuredCredential = resolveConfiguredCredential(flags, environment, stored);
92
101
  const baseUrl = resolveBaseUrl(flags, environment, stored);
93
102
  const project = resolveProjectContext({
94
103
  flags,
@@ -98,11 +107,8 @@ export function resolveTolokaConfig(
98
107
  effectiveBaseUrl: baseUrl,
99
108
  });
100
109
  const credential: CliCredential | undefined =
101
- apiKey !== undefined
102
- ? { type: "apiKey", apiKey }
103
- : browserLogin !== undefined
104
- ? { type: "browser", provider: browserLogin }
105
- : undefined;
110
+ configuredCredential ??
111
+ (browserLogin === undefined ? undefined : { type: "browser", provider: browserLogin });
106
112
  assertCli(credential, MISSING_CREDENTIALS_MESSAGE, { hint: { disabled: true } });
107
113
  return { baseUrl, credential, ...(project === undefined ? {} : { projectId: project.id }) };
108
114
  }