@toloka-platform/sdk 0.11.3 → 0.11.4

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
- 6f99f48f6bd7c933cd5d2f6b2382d9c4149e6cfb4a39a32364d49994a16f436e toloka.js.jsc
1
+ a7b5f9f339e9293bf388a7a57c15cfa4297942cdb7243e8b53d116661ddf5f0d toloka.js.jsc
@@ -1 +1 @@
1
- a038b95e14fe030c3d9b59182799dc89f8c76fd501fe42036831ea3d1daa27c5 toloka.js
1
+ 3c36c2751f8365c8ed466bd389723fa1d836d2ec5e61850450e4a725648ec0f8 toloka.js
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@toloka-platform/sdk",
3
- "version": "0.11.3",
3
+ "version": "0.11.4",
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": "113d50a57cb089999d1f93567c5c51afff932e67"
80
+ "commit": "7f1f084ec6f6d02475f766f468ad60a31ff123c8"
81
81
  }
82
82
  }
@@ -11,8 +11,8 @@
11
11
  "testing",
12
12
  "checks"
13
13
  ],
14
- "version": "0.11.3",
15
- "digest": "f56be02fd1deb2f4d4221718f0b774ae8a32ae088926ac9103c4a1049191f515",
14
+ "version": "0.11.4",
15
+ "digest": "2a3f1b1cfa72c726b6255e3e93158c52d6a96108575327dd73479c2536c63167",
16
16
  "files": [
17
17
  "SKILL.md",
18
18
  "references/human-work.md",
@@ -33,7 +33,7 @@
33
33
  "bug",
34
34
  "report"
35
35
  ],
36
- "version": "0.11.3",
36
+ "version": "0.11.4",
37
37
  "digest": "70f0bd2c80b32f87896aeb27c363b8a2d37f40e22424f57fe1aad142e125ad67",
38
38
  "files": [
39
39
  "SKILL.md",
@@ -50,7 +50,7 @@
50
50
  "getting-started",
51
51
  "agent"
52
52
  ],
53
- "version": "0.11.3",
53
+ "version": "0.11.4",
54
54
  "digest": "2278adeb46a792356579395fcf73c42322cb23bcd0c61e20856ccabdad6762d1",
55
55
  "files": [
56
56
  "SKILL.md"
@@ -67,7 +67,7 @@
67
67
  "sft",
68
68
  "models"
69
69
  ],
70
- "version": "0.11.3",
70
+ "version": "0.11.4",
71
71
  "digest": "b7ae3d76a6f1cba8d26a0dec30f540ef5a0298a9234580f418831c38cdc45bbb",
72
72
  "files": [
73
73
  "SKILL.md"
@@ -84,7 +84,7 @@
84
84
  "prompt",
85
85
  "models"
86
86
  ],
87
- "version": "0.11.3",
87
+ "version": "0.11.4",
88
88
  "digest": "25388767c582e064f9ba366b04ed3a9883e7e5926301efb2b352f06cd2fa1bab",
89
89
  "files": [
90
90
  "SKILL.md"
@@ -105,7 +105,7 @@ Choose representative fixtures from available data, redacting sensitive values w
105
105
 
106
106
  ## Recover from expected failures
107
107
 
108
- When plan, apply, or validate fails, follow [Recover safely](references/workflow-and-cli.md#recover-safely). It covers a dataset field schema conflict, an unreadable field schema, a missing secret, a 409 stale graph version, node drift after apply, and a server 400 after a partial apply. Never retry blindly, and never work around reconciliation with raw API calls.
108
+ When plan, apply, or validate fails, follow [Recover safely](references/workflow-and-cli.md#recover-safely). It covers a dataset field schema conflict, an unreadable field schema, a missing secret, a 409 stale graph version, node drift after apply, a server 400 after a partial apply, and a lost platform connection. Never retry blindly, and never work around reconciliation with raw API calls.
109
109
 
110
110
  ## Run an applied pipeline
111
111
 
@@ -107,13 +107,15 @@ The upload file path resolves from the command process's working directory, not
107
107
 
108
108
  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. When the dataset does not exist and no pipeline in source declares it, upload refuses, because it would have no column names to map onto; an existing dataset accepts the upload either way. 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.
109
109
 
110
+ After the upload the CLI prints the field types it inferred from the rows. They describe the rows, not the dataset's columns: inference omits enum, length, range, and format constraints, while the columns keep the schemas declared in source. Never edit `field()` declarations to match that list; `toloka plan` is what compares source with the live columns.
111
+
110
112
  If a later upload fails with a schema-incompatibility error, inspect the inferred schema from the first successful upload:
111
113
 
112
114
  ```sh
113
115
  toloka dataset fields <dataset>
114
116
  ```
115
117
 
116
- 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.
118
+ That prints the same inferred types as `field()` declarations for the top-level columns. Use them to recover stored names and base types, never to replace stricter source schemas. The command does not write source or mutate the dataset.
117
119
 
118
120
  Keep item files outside the source tree or gitignored. Never embed sample or production rows in `pipeline.ts` to make reconciliation create them.
119
121
 
@@ -178,3 +180,7 @@ Object settings preserve omitted live keys, but list-valued settings such as con
178
180
  ### Server 400 after a clean validate
179
181
 
180
182
  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. If the rejection means a newly created field schema is wrong, that field is already immutable: follow the field schema conflict rule, prefer the option that preserves names the request fixes, and report the orphaned field instead of deleting it. Check connection endpoints, duplicate or unknown imported IDs, field references, and accidentally authored layout. Never bypass reconciliation with a raw API call.
183
+
184
+ ### Platform connection lost
185
+
186
+ Inside a platform agent sandbox the CLI reaches the platform through the sandbox's connection and retries, for about 90 seconds, every request that connection reports as never sent. `Hint: The sandbox lost its connection to the platform … the request was not sent` therefore means nothing changed on the platform: retry the same command in a minute. Any other `5xx` may have reached the platform, so before repeating a command that changes state (`apply`, `dataset upload`, `pipeline run`), check whether it already took effect with the matching list or status command.
package/src/cli/data.ts CHANGED
@@ -132,7 +132,7 @@ const summarizeJsonSchema = (schema: unknown): string => {
132
132
  const formatInferredFields = (fields: ReadonlyArray<InferredField>): string => {
133
133
  if (fields.length === 0) return "No inferred fields are available yet.";
134
134
  return [
135
- "Inferred fields:",
135
+ "Types inferred from the uploaded rows (not the column schemas, which keep the constraints declared in source):",
136
136
  ...fields
137
137
  .toSorted((left, right) => left.jsonPath.localeCompare(right.jsonPath))
138
138
  .map(
@@ -459,6 +459,9 @@ export async function uploadDataset(
459
459
  };
460
460
  }
461
461
 
462
+ const INFERRED_DECLARATIONS_NOTE =
463
+ "// Inferred from the uploaded rows: enum, length, range and format constraints are not inferred, so these are not the dataset's column schemas.";
464
+
462
465
  export async function generateDatasetFieldDeclarations(
463
466
  client: PotatoClient,
464
467
  options: GenerateDatasetFieldDeclarationsOptions,
@@ -476,7 +479,7 @@ export async function generateDatasetFieldDeclarations(
476
479
  output:
477
480
  topLevelFields.length === 0
478
481
  ? `Dataset ${resolved.name} has no inferred fields yet.`
479
- : declarations,
482
+ : `${INFERRED_DECLARATIONS_NOTE}\n${declarations}`,
480
483
  };
481
484
  }
482
485
 
@@ -24,6 +24,9 @@ const apiErrorHint = (error: PotatoApiError): string | undefined => {
24
24
  if (error.status === 429) {
25
25
  return "Hint: The platform is rate limiting this key; wait a moment and retry.";
26
26
  }
27
+ if (error.status === 503 && error.body === "Tunnel unavailable; the request was not sent") {
28
+ return "Hint: The sandbox lost its connection to the platform (usually a platform restart) and it was not restored within 90 seconds; the request was not sent. Retry in a minute.";
29
+ }
27
30
  if (error.status !== undefined && error.status >= 500) {
28
31
  return "Hint: The platform failed to handle the request. Retry; if it keeps failing, write a report as the toloka-report-issue skill describes and send it with toloka report <report.md>.";
29
32
  }
package/src/transport.ts CHANGED
@@ -1,4 +1,4 @@
1
- import { assert } from "es-toolkit";
1
+ import { assert, delay } from "es-toolkit";
2
2
  import { PotatoApiError } from "./errors";
3
3
 
4
4
  const BASE_URL_HEADER = "x-potato-sdk-base-url";
@@ -14,9 +14,16 @@ export type ClientCredential =
14
14
  }
15
15
  | { readonly scopedToken: string; readonly accessToken?: undefined; readonly apiKey?: undefined };
16
16
 
17
+ const RELAY_HEADER = "x-potato-relay";
18
+ const TUNNEL_UNAVAILABLE = "tunnel-unavailable";
19
+ export const TUNNEL_RETRY_DELAYS_MS: readonly number[] = [
20
+ 1_000, 2_000, 4_000, 8_000, 15_000, 15_000, 15_000, 15_000, 15_000,
21
+ ];
22
+
17
23
  type TransportRequestInit = RequestInit & {
18
24
  readonly fetch?: typeof globalThis.fetch;
19
25
  readonly authorization?: () => Promise<string>;
26
+ readonly tunnelRetryDelaysMs?: readonly number[];
20
27
  };
21
28
 
22
29
  export const createRequestOptions = (
@@ -79,6 +86,7 @@ export async function potatoFetch(url: string, options: TransportRequestInit): P
79
86
  const {
80
87
  fetch: fetchImplementation = globalThis.fetch,
81
88
  authorization,
89
+ tunnelRetryDelaysMs = TUNNEL_RETRY_DELAYS_MS,
82
90
  ...requestOptions
83
91
  } = options;
84
92
  const headers = new Headers(requestOptions.headers);
@@ -87,10 +95,20 @@ export async function potatoFetch(url: string, options: TransportRequestInit): P
87
95
  headers.delete(BASE_URL_HEADER);
88
96
  if (authorization !== undefined) headers.set("Authorization", await authorization());
89
97
 
90
- const response = await fetchImplementation(
91
- resolveRequestUrl(url, baseUrl),
92
- prepareRequestInit(requestOptions, headers),
93
- );
98
+ const send = () =>
99
+ fetchImplementation(resolveRequestUrl(url, baseUrl), prepareRequestInit(requestOptions, headers));
100
+
101
+ let response = await send();
102
+ const replayableBody = !(typeof ReadableStream !== "undefined" && requestOptions.body instanceof ReadableStream);
103
+
104
+ for (const delayMs of replayableBody ? tunnelRetryDelaysMs : []) {
105
+ if (response.status !== 503 || response.headers.get(RELAY_HEADER) !== TUNNEL_UNAVAILABLE) break;
106
+
107
+ await response.body?.cancel();
108
+ await delay(delayMs, { signal: requestOptions.signal ?? undefined });
109
+ response = await send();
110
+ }
111
+
94
112
  const text = await response.text();
95
113
 
96
114
  if (!response.ok) {