@toloka-platform/sdk 0.5.1 → 0.6.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Binary file
@@ -1 +1 @@
1
- 115309d50a2755a511f097ac14ea83f5cd03fa198fb0e975e64ad9538c2e3474 toloka.js.jsc
1
+ 4152a6a66243067c73d45dd077a87a1705b645b14e36c558263663f4939006a4 toloka.js.jsc
@@ -1 +1 @@
1
- 5f0222c5ad52de0073174b3fcc1d9a3433262a803f2e2be183337d8dc3106215 toloka.js
1
+ 5dac0697d5d6357e396c06f60b30f99939d2b4cba45130a15effdce678045f48 toloka.js
package/docs/cli.md CHANGED
@@ -137,8 +137,16 @@ toloka project url
137
137
  toloka project remove <alias>
138
138
  toloka project create [alias] [--name <name>] [--team-id <id>] [--delivery-order-id <id>]
139
139
  toloka project untrack <pipeline|dataset> <name> [--confirm]
140
+ toloka project token [--preset <name>] [--permissions <list>] [--expires-in <seconds>] [--projects <list>]
141
+ toloka project tokens
142
+ toloka project revoke-token <tokenId>
143
+ toloka project revoke-tokens
140
144
  ```
141
145
 
146
+ `token` mints a project-scoped token for the selected project and prints it once; it is never stored. The token acts as you inside that project (plus any `--projects`, which must belong to the same team) with the permissions of the `--preset` (`read`, `write`, `deploy`, `labeling-read`, `labeling-write`, `labeling-deploy`; default `write`) or an explicit `--permissions` list, and expires after `--expires-in` seconds (60..21600, default six hours). Hand it to an agent as `TOLOKA_API_KEY`. One user may hold at most 1000 live tokens per project, counted against every project the token covers; beyond that, `token` fails with `429 You already hold 1000 live tokens for project <id>; revoke unused tokens first`.
147
+
148
+ `tokens` lists every live token of the selected project, newest first, with its id, user, expiry, projects, and permissions. `revoke-token <tokenId>` revokes one token and prints `Revoked <tokenId>.`; `revoke-tokens` revokes every live token of the selected project in one call and prints `Revoked <n> tokens.`. A token that covers several projects is revoked as a whole by either command. Revocation takes effect immediately.
149
+
142
150
  `add`, `switch`, `remove`, and `create` write the machine registry in the credentials file. They refuse `--api-key` / `--base-url` / `TOLOKA_*` when those would select a different key or platform than the stored file. `project list` prints project id, name, and team id. `create` uses the account's only writable team (`GET /me` lists the teams; `whoami` prints them) and asks for `--team-id` when there are several. A project owned by an internal team also needs `--delivery-order-id`; `toloka delivery-order list` shows the orders and answers with an explanation for accounts without internal teams.
143
151
 
144
152
  `project untrack` removes one pipeline or dataset binding from `toloka.json` without deleting the live resource. It previews the name and id in a terminal; outside a terminal, pass `--confirm` after reviewing the state file.
@@ -57,6 +57,7 @@ import type {
57
57
  ListProjectPipelinesParams,
58
58
  ListProjectSecrets200,
59
59
  ListProjectTokens200,
60
+ ListProjectTokensParams,
60
61
  ListProjectTrainings200,
61
62
  ListProjectTrainingsParams,
62
63
  ListProjects200,
@@ -83,6 +84,7 @@ import type {
83
84
  ProjectSecretName,
84
85
  ReplacePipelineGraphBody,
85
86
  ReplaceTrainingConfigurationBody,
87
+ RevokedScopedTokens,
86
88
  RunCost,
87
89
  RunId,
88
90
  RunItem,
@@ -1646,7 +1648,37 @@ export const createProjectToken = async (projectId: ProjectId,
1646
1648
 
1647
1649
 
1648
1650
 
1649
- export const getListProjectTokensUrl = (projectId: ProjectId,) => {
1651
+ export const getListProjectTokensUrl = (projectId: ProjectId,
1652
+ params?: ListProjectTokensParams,) => {
1653
+ const normalizedParams = new URLSearchParams();
1654
+
1655
+ Object.entries(params || {}).forEach(([key, value]) => {
1656
+
1657
+ if (value !== undefined) {
1658
+ normalizedParams.append(key, value === null ? 'null' : value.toString())
1659
+ }
1660
+ });
1661
+
1662
+ const stringifiedParams = normalizedParams.toString();
1663
+
1664
+ return stringifiedParams.length > 0 ? `/api/v2-beta/projects/${projectId}/tokens?${stringifiedParams}` : `/api/v2-beta/projects/${projectId}/tokens`
1665
+ }
1666
+
1667
+ export const listProjectTokens = async (projectId: ProjectId,
1668
+ params?: ListProjectTokensParams, options?: RequestInit): Promise<ListProjectTokens200> => {
1669
+
1670
+ return potatoFetch<ListProjectTokens200>(getListProjectTokensUrl(projectId,params),
1671
+ {
1672
+ ...options,
1673
+ method: 'GET'
1674
+
1675
+
1676
+ }
1677
+ );}
1678
+
1679
+
1680
+
1681
+ export const getRevokeProjectTokensUrl = (projectId: ProjectId,) => {
1650
1682
 
1651
1683
 
1652
1684
 
@@ -1654,12 +1686,12 @@ export const getListProjectTokensUrl = (projectId: ProjectId,) => {
1654
1686
  return `/api/v2-beta/projects/${projectId}/tokens`
1655
1687
  }
1656
1688
 
1657
- export const listProjectTokens = async (projectId: ProjectId, options?: RequestInit): Promise<ListProjectTokens200> => {
1689
+ export const revokeProjectTokens = async (projectId: ProjectId, options?: RequestInit): Promise<RevokedScopedTokens> => {
1658
1690
 
1659
- return potatoFetch<ListProjectTokens200>(getListProjectTokensUrl(projectId),
1691
+ return potatoFetch<RevokedScopedTokens>(getRevokeProjectTokensUrl(projectId),
1660
1692
  {
1661
1693
  ...options,
1662
- method: 'GET'
1694
+ method: 'DELETE'
1663
1695
 
1664
1696
 
1665
1697
  }
@@ -312,6 +312,7 @@ export * from './listProjectPipelines200';
312
312
  export * from './listProjectPipelinesParams';
313
313
  export * from './listProjectSecrets200';
314
314
  export * from './listProjectTokens200';
315
+ export * from './listProjectTokensParams';
315
316
  export * from './listProjectTrainings200';
316
317
  export * from './listProjectTrainingsParams';
317
318
  export * from './listProjects200';
@@ -784,6 +785,7 @@ export * from './quorumNodeSettingsSmartSearchAnyOfProjectDescription';
784
785
  export * from './quorumNodeSettingsUi';
785
786
  export * from './replacePipelineGraphBody';
786
787
  export * from './replaceTrainingConfigurationBody';
788
+ export * from './revokedScopedTokens';
787
789
  export * from './rootNode';
788
790
  export * from './rootNodeSettings';
789
791
  export * from './rootNodeSettingsCompletedFieldProxies';
@@ -1,5 +1,7 @@
1
1
  import type { ScopedTokenSummary } from './scopedTokenSummary';
2
+ import type { Pagination } from './pagination';
2
3
 
3
4
  export type ListProjectTokens200 = {
4
5
  tokens: ScopedTokenSummary[];
6
+ pagination: Pagination;
5
7
  };
@@ -0,0 +1,5 @@
1
+
2
+ export type ListProjectTokensParams = {
3
+ limit?: number;
4
+ offset?: number;
5
+ };
@@ -0,0 +1,4 @@
1
+
2
+ export interface RevokedScopedTokens {
3
+ revokedCount: number;
4
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@toloka-platform/sdk",
3
- "version": "0.5.1",
3
+ "version": "0.6.0",
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": "f42223610fd0991694e1fb7852690b9b9a42e565"
80
+ "commit": "4658b9a88d4322ec5c9bc8cedbc0f31e0150a859"
81
81
  }
82
82
  }
@@ -11,8 +11,8 @@
11
11
  "testing",
12
12
  "checks"
13
13
  ],
14
- "version": "0.5.1",
15
- "digest": "d10767a13e558bb74e64768dbcc5260e8f78b5541050ed5cb6d5eb9809733107",
14
+ "version": "0.6.0",
15
+ "digest": "c17a1c4f9b3b974ae07859bc3ac7669d98d47d9984e174712f550ef7c3204d63",
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.5.1",
32
+ "version": "0.6.0",
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.5.1",
49
+ "version": "0.6.0",
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.5.1",
66
+ "version": "0.6.0",
67
67
  "digest": "f9c34569fc344f365c79c86819adffc86c78a2ce74e17ba10755ddefaf71790f",
68
68
  "files": [
69
69
  "SKILL.md"
@@ -80,7 +80,7 @@
80
80
  "prompt",
81
81
  "models"
82
82
  ],
83
- "version": "0.5.1",
83
+ "version": "0.6.0",
84
84
  "digest": "902aee47c952e105a6340ee56bf22e79513e2d45d19a7ffbf58990f277387e34",
85
85
  "files": [
86
86
  "SKILL.md"
@@ -76,7 +76,7 @@ Use `node_modules/@toloka-platform/sdk/examples/face-image-collection/` as a con
76
76
 
77
77
  Distinguish server requirements from optional design choices. The following constraints and defaults are not all checked by `definePipeline`:
78
78
 
79
- - **Do not pair `projectType: 'general'` with `verifiedExperts: false`.** The server answers `General projects require an ID-verified audience`. Specializations also require a verified audience.
79
+ - **Specializations require a verified audience.** Pair `audience.specializations` with `verifiedExperts: true`. Any project type may use the broad unverified crowd; the default is verified, so set `verifiedExperts: false` only when the task asks for it.
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.
@@ -117,7 +117,7 @@ Use `generation` for a single human-in-the-loop task type:
117
117
 
118
118
  Use `about.projectType: 'general'` when experts act on existing items: annotation, classification, judgment, ranking, rating, comparison, or rewriting.
119
119
 
120
- Use `about.projectType: 'data_collection'` when experts produce new original content instead of labeling an existing item. Build the UI with the corresponding upload or recording component. Prefer the larger unverified crowd for this work by setting `audienceAndPricing.verifiedExperts: false` unless the task specifically requires verified expertise. Do not use an unverified audience for a `general` project.
120
+ Use `about.projectType: 'data_collection'` when experts produce new original content instead of labeling an existing item. Build the UI with the corresponding upload or recording component.
121
121
 
122
122
  ### Configure it
123
123
 
@@ -164,7 +164,7 @@ Keep `instruction` actionable and in English. Include the workflow, concepts, qu
164
164
 
165
165
  #### Audience and pricing
166
166
 
167
- 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. Derive languages and expertise from the task and its inputs, leaving specialization and country filters unrestricted unless needed. English is a reasonable starting language for an English-language task, not a universal audience default. Ask when an unresolved audience requirement changes who can do the work. Do not put specializations on an unverified audience. A specialization's `qualificationId` is platform identity: preserve an imported value, and discover new ones with `toloka audience specialization list`, copying `ID` into `qualificationId` and `NAME` into `value`; never invent one.
167
+ For humans, set `audienceType: 'human'`, `verifiedExperts`, all three audience filters, `pricePerTask`, and `timeForTask`. Set `verifiedExperts: true` unless the task explicitly asks for the broad unverified crowd. 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. Derive languages and expertise from the task and its inputs, leaving specialization and country filters unrestricted unless needed. English is a reasonable starting language for an English-language task, not a universal audience default. Ask when an unresolved audience requirement changes who can do the work. Do not put specializations on an unverified audience. A specialization's `qualificationId` is platform identity: preserve an imported value, and discover new ones with `toloka audience specialization list`, copying `ID` into `qualificationId` and `NAME` into `value`; never invent one.
168
168
 
169
169
  `$0.20` and 120 seconds are a starting point for a short task, not a universal rate. Estimate effort from the work and required expertise, respect explicit rates, and explain meaningful adjustments in the cost forecast. The per-expert cap is normally off. For collection tasks, choose a cap based on contributor diversity and repetition requirements; 1 fits one-response-per-expert collection. Avoid copying an arbitrary cap into a task that does not need one.
170
170
 
@@ -187,7 +187,8 @@ Use `reuseAudienceWithNodeId` only with real imported `{ pipelineId, nodeId }` v
187
187
  - Do not add fields, explanations, comments, or labor the task does not require.
188
188
  - Do not choose synthetic generation merely because a model could do the task; require an explicit request.
189
189
  - Do not treat the constructor's generated placeholder Training or blank UI as production-ready configuration.
190
- - 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.
190
+ - Do not pair `audience.specializations` with `verifiedExperts: false`. Specializations are qualification-backed, and only verified experts hold them.
191
+ - Do not flip an explicitly requested `verifiedExperts: false` to `true`. Every project type accepts either audience mode.
191
192
  - 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.
192
193
  - Do not omit `maxCostPerAttempt` from a synthetic `audienceAndPricing`. Omitting it fails validation with `must have required property 'maxCostPerAttempt'`, reported together with a misleading `type must be equal to constant` and the human branch's `must have required property 'verifiedExperts'` from the failed `oneOf` discrimination.
193
194
  - 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`.
@@ -92,6 +92,12 @@ revoke an API key.
92
92
 
93
93
  Standalone and Bun-global `toloka` installations check the public npm registry at most once per day and install eligible releases at least 24 hours old before rerunning the command. Project-local installs, CI, and `config`, `version`, and `help` skip the check. Turn it off with `toloka config set auto-update false`. For manual standalone updates, rerun `curl -fsSL https://platform.toloka.ai/cli/install.sh | sh -s -- --native`; Bun-global updates should use `--bun` instead. Preserve the mode and put any original `TOLOKA_CLI_INSTALL_DIR`, `TOLOKA_CLI_CHANNEL`, `TOLOKA_CLI_PACKAGE`, and `TOLOKA_CLI_REGISTRY` overrides on the `sh` side of the pipe again. A package version pin does not disable automatic updates, which always use the public npm registry.
94
94
 
95
+ ## Hand out a project-scoped token
96
+
97
+ `toloka project token` mints a token that acts as the signed-in user inside the selected project only (`--projects` adds more projects of the same team), with the permissions of `--preset` (default `write`) or an explicit `--permissions` list, for at most six hours. It is printed once and never stored; pass it to another agent or script as `TOLOKA_API_KEY`. `toloka project tokens` lists the live tokens of the project; `toloka project revoke-token <tokenId>` ends one and `toloka project revoke-tokens` ends every live token of the project at once, for example when a run finished or a token leaked. One user may hold at most 1000 live tokens per project; when `token` fails with `429 … revoke unused tokens first`, run `revoke-tokens`.
98
+
99
+ A scoped token downloads project files in two steps: a request to `ProjectFile.url` with the token answers `200 {"url": "<storage URL>"}` instead of the redirect a human credential gets. Fetch that `url` as a plain request without the `X-Scoped-Token` header; HTTP clients replay custom headers across redirects, and the token must never reach the storage host.
100
+
95
101
  ## Configure the CLI
96
102
 
97
103
  Provide the API key and base URL on networked workflows through `TOLOKA_API_KEY` and
@@ -126,6 +132,10 @@ toloka project url
126
132
  toloka project remove <alias>
127
133
  toloka project create [alias] [--name <name>] [--team-id <id>] [--delivery-order-id <id>]
128
134
  toloka project untrack <pipeline|dataset> <name> [--confirm]
135
+ toloka project token [--preset <name>] [--permissions <list>] [--expires-in <seconds>] [--projects <list>]
136
+ toloka project tokens
137
+ toloka project revoke-token <tokenId>
138
+ toloka project revoke-tokens
129
139
  toloka delivery-order list
130
140
  toloka audience specialization list [--json]
131
141
  toloka init
@@ -14,7 +14,7 @@ import { addProject, assertProjectSelected, createProject, currentProject, listR
14
14
  import { readTolokaState } from "../../state";
15
15
  import { assertCli } from "../cli-error";
16
16
  import { formatTable } from "../table";
17
- import type { CreateScopedTokenBodyAnyOfPreset } from "../../../openapi/gen/models";
17
+ import type { CreateScopedTokenBodyAnyOfPreset, ScopedTokenSummary } from "../../../openapi/gen/models";
18
18
 
19
19
  const TOKEN_PRESETS: readonly CreateScopedTokenBodyAnyOfPreset[] = [
20
20
  "read",
@@ -183,7 +183,10 @@ export const registerProjectCommands = (program: Command, host: CommandHost): vo
183
183
  .action(async () => {
184
184
  const { client, projectId } = await resolveContext(globals(), context);
185
185
  assertProjectSelected(projectId);
186
- const { tokens } = await client.projects.listTokens({ projectId });
186
+ const tokens: ScopedTokenSummary[] = [];
187
+ for await (const token of client.projects.listAllTokens({ projectId })) {
188
+ tokens.push(token);
189
+ }
187
190
  context.io.stdout(
188
191
  tokens.length === 0
189
192
  ? "No live tokens."
@@ -209,6 +212,15 @@ export const registerProjectCommands = (program: Command, host: CommandHost): vo
209
212
  await client.projects.revokeToken({ projectId, tokenId });
210
213
  context.io.stdout(`Revoked ${tokenId}.`);
211
214
  });
215
+ project
216
+ .command("revoke-tokens")
217
+ .description("revoke every live project-scoped token of the project; they stop working immediately")
218
+ .action(async () => {
219
+ const { client, projectId } = await resolveContext(globals(), context);
220
+ assertProjectSelected(projectId);
221
+ const { revokedCount } = await client.projects.revokeAllTokens({ projectId });
222
+ context.io.stdout(`Revoked ${revokedCount} token${revokedCount === 1 ? "" : "s"}.`);
223
+ });
212
224
  const create = project
213
225
  .command("create")
214
226
  .argument("[alias]", "local name for the new project")
package/src/client.ts CHANGED
@@ -37,6 +37,7 @@ import type {
37
37
  ListAudienceSpecializationsParams,
38
38
  ListProjectModelsParams,
39
39
  ListProjectsParams,
40
+ ListProjectTokensParams,
40
41
  ListTrainingRunsParams,
41
42
  ListProjectTrainingsParams,
42
43
  Pipeline as GeneratedPipeline,
@@ -292,10 +293,17 @@ const createClientImplementation = (config: ClientConfig) => {
292
293
  }, params),
293
294
  createToken: ({ projectId, ...body }: { projectId: ProjectId } & CreateScopedTokenBody) =>
294
295
  wire.createProjectToken(projectId, body, requestOptions),
295
- listTokens: ({ projectId }: { projectId: ProjectId }) =>
296
- wire.listProjectTokens(projectId, requestOptions),
296
+ listTokens: ({ projectId, ...params }: { projectId: ProjectId } & ListProjectTokensParams) =>
297
+ wire.listProjectTokens(projectId, params, requestOptions),
298
+ listAllTokens: ({ projectId, ...params }: { projectId: ProjectId } & ListProjectTokensParams) =>
299
+ listAllPages(async (pageOptions) => {
300
+ const page = await wire.listProjectTokens(projectId, { ...params, ...pageOptions }, requestOptions);
301
+ return { items: page.tokens, pagination: page.pagination };
302
+ }, params),
297
303
  revokeToken: ({ projectId, tokenId }: { projectId: ProjectId; tokenId: string }) =>
298
304
  wire.revokeProjectToken(projectId, tokenId, requestOptions),
305
+ revokeAllTokens: ({ projectId }: { projectId: ProjectId }) =>
306
+ wire.revokeProjectTokens(projectId, requestOptions),
299
307
  revokeCurrentToken: () => wire.revokeCurrentToken(requestOptions),
300
308
  };
301
309