@zapier/zapier-sdk 0.102.0 → 0.102.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.
package/CHANGELOG.md CHANGED
@@ -1,5 +1,22 @@
1
1
  # @zapier/zapier-sdk
2
2
 
3
+ ## 0.102.2
4
+
5
+ ### Patch Changes
6
+
7
+ - b6dfdf9: `runDurable`, `publishWorkflowVersion` and `updateWorkflowDraft` document that `"latest"` is accepted for the durable runtime version and, in `dependencies`, for `@zapier/zapier-durable` and `@zapier/zapier-sdk`. The server resolves those to the newest release old enough to install — not npm's `"latest"` tag, which can point at a release too new to install. A `"latest"` on any other package is passed to the install unchanged. On a draft the value is stored verbatim and resolved when the draft is published.
8
+
9
+ ## 0.102.1
10
+
11
+ ### Patch Changes
12
+
13
+ - 173df80: `ZapierRateLimitError` now surfaces the server's explanatory message — quota used, when the window resets, and how to request a higher limit — instead of the bare string "Rate limited". When the response body is empty or unreadable, the message falls back to "Rate limited" as before.
14
+
15
+ `rateLimit.resetMs` on the error is now a real epoch timestamp when the server sends `X-RateLimit-Reset` as seconds-until-reset (the IETF draft convention) instead of a date in 1970. This also fixes the retry delay computed from that header when `Retry-After` is absent, which previously collapsed to zero and retried immediately.
16
+
17
+ - Updated dependencies [173df80]
18
+ - @zapier/kitcore@0.17.2
19
+
3
20
  ## 0.102.0
4
21
 
5
22
  ### Minor Changes
package/README.md CHANGED
@@ -2088,23 +2088,23 @@ Publish a new version of a durable workflow. Enables the workflow by default.
2088
2088
 
2089
2089
  **Parameters:**
2090
2090
 
2091
- | Name | Type | Required | Default | Possible Values | Description |
2092
- | -------------------------- | --------- | -------- | ------- | --------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
2093
- | `options` | `object` | ✅ | — | — | |
2094
- | ​ ↳ `workflow` | `string` | ✅ | — | — | Durable workflow ID |
2095
- | ​ ↳ `sourceFiles` | `object` | ✅ | — | — | Source files keyed by filename → contents |
2096
- | ​ ↳ `dependencies` | `object` | ❌ | — | — | Optional npm package dependencies |
2097
- | ​ ↳ `zapierDurableVersion` | `string` | ❌ | — | — | Exact semver of @zapier/zapier-durable to use (e.g. "1.2.3"). Defaults to server-configured version if omitted. |
2098
- | ​ ↳ `enabled` | `boolean` | ❌ | — | — | Enable the workflow after publishing. Defaults to true if omitted; pass false to publish without enabling. |
2099
- | ​ ↳ `ignoreOpenDrafts` | `boolean` | ❌ | — | — | Publish even though the workflow has open draft(s). Without this, the API rejects a direct publish with a 409 while any draft is open, since publishing the draft later would ship its stale content over this version. |
2100
- | ​ ↳ `connections` | `object` | ❌ | — | — | Map of connection aliases to Zapier connections used by the workflow. Pass `null` to clear an existing binding. |
2101
- | ​ ↳ `appVersions` | `object` | ❌ | — | — | Map of app keys to pinned app implementation/version used by the workflow. Pass `null` to clear an existing binding. |
2102
- | ​ ↳ `trigger` | `object` | ❌ | — | — | Trigger configuration. When provided, the workflow subscribes to a Zapier trigger; for an on-demand, triggerless workflow, omit this and pass `manual: true` instead. |
2103
- | ​   ↳ `selectedApi` | `string` | ❌ | — | — | Zapier app/API identifier (e.g. 'GoogleSheetsAPI'). Required when a trigger is configured. |
2104
- | ​   ↳ `action` | `string` | ✅ | — | — | Trigger action key (e.g. 'new_row') |
2105
- | ​   ↳ `authenticationId` | `string` | ❌ | — | — | Connection ID for the trigger source. Omit or pass null for no-auth triggers (e.g. Schedule by Zapier). |
2106
- | ​   ↳ `params` | `object` | ❌ | — | — | Trigger parameters as a JSON object |
2107
- | ​ ↳ `manual` | `boolean` | ❌ | — | — | Declare this an on-demand, triggerless workflow version. Pass `manual: true` only when omitting `trigger`; passing both is a contradiction the API rejects with a 400. |
2091
+ | Name | Type | Required | Default | Possible Values | Description |
2092
+ | -------------------------- | --------- | -------- | ------- | --------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
2093
+ | `options` | `object` | ✅ | — | — | |
2094
+ | ​ ↳ `workflow` | `string` | ✅ | — | — | Durable workflow ID |
2095
+ | ​ ↳ `sourceFiles` | `object` | ✅ | — | — | Source files keyed by filename → contents |
2096
+ | ​ ↳ `dependencies` | `object` | ❌ | — | — | Optional npm package dependencies. A version of "latest" is resolved by the server for @zapier/zapier-durable and @zapier/zapier-sdk only; every other value, including a "latest" on any other package, is passed to the install as written. |
2097
+ | ​ ↳ `zapierDurableVersion` | `string` | ❌ | — | — | Exact semver of @zapier/zapier-durable to use (e.g. "1.2.3"), or "latest". Defaults to the server-configured version if omitted. "latest" is resolved by the server to the newest release old enough to clear the sandbox install age gate (24h) -- this is not npm's "latest" tag, which can point at a release too new to install. Ranges (^1.2.3, ~1.2) are rejected. |
2098
+ | ​ ↳ `enabled` | `boolean` | ❌ | — | — | Enable the workflow after publishing. Defaults to true if omitted; pass false to publish without enabling. |
2099
+ | ​ ↳ `ignoreOpenDrafts` | `boolean` | ❌ | — | — | Publish even though the workflow has open draft(s). Without this, the API rejects a direct publish with a 409 while any draft is open, since publishing the draft later would ship its stale content over this version. |
2100
+ | ​ ↳ `connections` | `object` | ❌ | — | — | Map of connection aliases to Zapier connections used by the workflow. Pass `null` to clear an existing binding. |
2101
+ | ​ ↳ `appVersions` | `object` | ❌ | — | — | Map of app keys to pinned app implementation/version used by the workflow. Pass `null` to clear an existing binding. |
2102
+ | ​ ↳ `trigger` | `object` | ❌ | — | — | Trigger configuration. When provided, the workflow subscribes to a Zapier trigger; for an on-demand, triggerless workflow, omit this and pass `manual: true` instead. |
2103
+ | ​   ↳ `selectedApi` | `string` | ❌ | — | — | Zapier app/API identifier (e.g. 'GoogleSheetsAPI'). Required when a trigger is configured. |
2104
+ | ​   ↳ `action` | `string` | ✅ | — | — | Trigger action key (e.g. 'new_row') |
2105
+ | ​   ↳ `authenticationId` | `string` | ❌ | — | — | Connection ID for the trigger source. Omit or pass null for no-auth triggers (e.g. Schedule by Zapier). |
2106
+ | ​   ↳ `params` | `object` | ❌ | — | — | Trigger parameters as a JSON object |
2107
+ | ​ ↳ `manual` | `boolean` | ❌ | — | — | Declare this an on-demand, triggerless workflow version. Pass `manual: true` only when omitting `trigger`; passing both is a contradiction the API rejects with a 400. |
2108
2108
 
2109
2109
  **Returns:** `Promise<WorkflowVersionItem>`
2110
2110
 
@@ -2137,21 +2137,21 @@ Run a workflow source file as a run-once durable run on code-substrate-runner (n
2137
2137
 
2138
2138
  **Parameters:**
2139
2139
 
2140
- | Name | Type | Required | Default | Possible Values | Description |
2141
- | -------------------------- | ---------- | -------- | ------- | --------------- | --------------------------------------------------------------------------------------------------------------------------------------- |
2142
- | `options` | `object` | ✅ | — | — | |
2143
- | ​ ↳ `sourceFiles` | `object` | ✅ | — | — | Source files keyed by filename → contents |
2144
- | ​ ↳ `input` | `unknown` | ❌ | — | — | Input data passed to the run. Accepts any JSON value, or its JSON-string encoding. |
2145
- | ​ ↳ `dependencies` | `object` | ❌ | — | — | Optional npm package dependencies |
2146
- | ​ ↳ `zapierDurableVersion` | `string` | ❌ | — | — | Exact semver of @zapier/zapier-durable to use (e.g. "1.2.3"). Defaults to server-configured version if omitted. |
2147
- | ​ ↳ `connections` | `object` | ❌ | — | — | Named connection aliases. Maps each alias to an object holding its Zapier connection ID, e.g. `{ "slack": { "connectionId": "123" } }`. |
2148
- | ​ ↳ `appVersions` | `object` | ❌ | — | — | Pinned app versions. Maps app keys (slugs) to implementation names and versions. |
2149
- | ​ ↳ `private` | `boolean` | ❌ | — | — | Only the creating user can see the run (default false) |
2150
- | ​ ↳ `notifications[]` | `object[]` | ❌ | — | — | Webhook subscribers for run lifecycle events. Each entry specifies a URL and the events it subscribes to. |
2151
- | ​   ↳ `type` | `string` | ❌ | — | `webhook` | Notification transport. Webhook is the only supported type. |
2152
- | ​   ↳ `url` | `string` | ✅ | — | — | URL to POST event notifications to. Payload is `{run_id, event}`. |
2153
- | ​   ↳ `max_retries` | `number` | ❌ | — | — | Max delivery attempts with exponential backoff. Defaults to 3 server-side. |
2154
- | ​   ↳ `events` | `array` | ✅ | — | — | One or more lifecycle events to subscribe this URL to. |
2140
+ | Name | Type | Required | Default | Possible Values | Description |
2141
+ | -------------------------- | ---------- | -------- | ------- | --------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------ |
2142
+ | `options` | `object` | ✅ | — | — | |
2143
+ | ​ ↳ `sourceFiles` | `object` | ✅ | — | — | Source files keyed by filename → contents |
2144
+ | ​ ↳ `input` | `unknown` | ❌ | — | — | Input data passed to the run. Accepts any JSON value, or its JSON-string encoding. |
2145
+ | ​ ↳ `dependencies` | `object` | ❌ | — | — | Optional npm package dependencies. A version of "latest" is resolved by the server for @zapier/zapier-durable and @zapier/zapier-sdk only; every other value, including a "latest" on any other package, is passed to the install as written. |
2146
+ | ​ ↳ `zapierDurableVersion` | `string` | ❌ | — | — | Exact semver of @zapier/zapier-durable to use (e.g. "1.2.3"), or "latest". Defaults to the server-configured version if omitted. "latest" is resolved by the server to the newest release old enough to clear the sandbox install age gate (24h) -- this is not npm's "latest" tag, which can point at a release too new to install. Ranges (^1.2.3, ~1.2) are rejected. |
2147
+ | ​ ↳ `connections` | `object` | ❌ | — | — | Named connection aliases. Maps each alias to an object holding its Zapier connection ID, e.g. `{ "slack": { "connectionId": "123" } }`. |
2148
+ | ​ ↳ `appVersions` | `object` | ❌ | — | — | Pinned app versions. Maps app keys (slugs) to implementation names and versions. |
2149
+ | ​ ↳ `private` | `boolean` | ❌ | — | — | Only the creating user can see the run (default false) |
2150
+ | ​ ↳ `notifications[]` | `object[]` | ❌ | — | — | Webhook subscribers for run lifecycle events. Each entry specifies a URL and the events it subscribes to. |
2151
+ | ​   ↳ `type` | `string` | ❌ | — | `webhook` | Notification transport. Webhook is the only supported type. |
2152
+ | ​   ↳ `url` | `string` | ✅ | — | — | URL to POST event notifications to. Payload is `{run_id, event}`. |
2153
+ | ​   ↳ `max_retries` | `number` | ❌ | — | — | Max delivery attempts with exponential backoff. Defaults to 3 server-side. |
2154
+ | ​   ↳ `events` | `array` | ✅ | — | — | One or more lifecycle events to subscribe this URL to. |
2155
2155
 
2156
2156
  **Returns:** `Promise<DurableRunItem>`
2157
2157
 
@@ -2242,19 +2242,19 @@ Update (autosave) an open workflow draft
2242
2242
 
2243
2243
  **Parameters:**
2244
2244
 
2245
- | Name | Type | Required | Default | Possible Values | Description |
2246
- | -------------------------- | --------- | -------- | ------- | --------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
2247
- | `options` | `object` | ✅ | — | — | |
2248
- | ​ ↳ `workflow` | `string` | ✅ | — | — | Durable workflow ID |
2249
- | ​ ↳ `draft` | `string` | ✅ | — | — | Workflow draft ID |
2250
- | ​ ↳ `sourceFiles` | `object` | ✅ | — | — | Source files keyed by filename → contents |
2251
- | ​ ↳ `zapierDurableVersion` | `string` | ❌ | — | — | Exact semver of @zapier/zapier-durable to use (e.g. "1.2.3"). Leaves the stored pin unchanged if omitted. |
2252
- | ​ ↳ `dependencies` | `object` | ❌ | — | — | Optional npm package dependencies |
2253
- | ​ ↳ `draftRevision` | `number` | ❌ | — | — | Expected draft revision for optimistic concurrency. Pass the revision from the last read; the server rejects the save with a conflict if the draft has changed since. Omit to skip the check. |
2254
- | ​ ↳ `trigger` | `object` | ❌ | — | — | Trigger configuration. Omit to leave the stored trigger unchanged, pass null to clear it, or pass an object to replace it; for an on-demand, triggerless workflow, clear this and pass `manual: true` instead. |
2255
- | ​ ↳ `connections` | `object` | ❌ | — | — | Map of connection aliases to Zapier connections used by the workflow. Pass `null` to clear an existing binding. |
2256
- | ​ ↳ `appVersions` | `object` | ❌ | — | — | Map of app keys to pinned app implementation/version used by the workflow. Pass `null` to clear an existing binding. |
2257
- | ​ ↳ `manual` | `boolean` | ❌ | — | — | Declare this draft on-demand and triggerless. Pass `manual: true` only when omitting `trigger` (or clearing it with `trigger: null`); passing both is a contradiction the API rejects with a 400. Setting a `trigger` later clears a previously stored `manual: true`. |
2245
+ | Name | Type | Required | Default | Possible Values | Description |
2246
+ | -------------------------- | --------- | -------- | ------- | --------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
2247
+ | `options` | `object` | ✅ | — | — | |
2248
+ | ​ ↳ `workflow` | `string` | ✅ | — | — | Durable workflow ID |
2249
+ | ​ ↳ `draft` | `string` | ✅ | — | — | Workflow draft ID |
2250
+ | ​ ↳ `sourceFiles` | `object` | ✅ | — | — | Source files keyed by filename → contents |
2251
+ | ​ ↳ `zapierDurableVersion` | `string` | ❌ | — | — | Exact semver of @zapier/zapier-durable to use (e.g. "1.2.3"), or "latest". Leaves the stored pin unchanged if omitted. A draft stores what you pass verbatim, so "latest" is recorded as-is and resolved to an exact version when the draft is published -- reading the draft back shows "latest", not a version. Ranges (^1.2.3, ~1.2) are rejected at publish. |
2252
+ | ​ ↳ `dependencies` | `object` | ❌ | — | — | Optional npm package dependencies. A version of "latest" is resolved by the server for @zapier/zapier-durable and @zapier/zapier-sdk only; every other value, including a "latest" on any other package, is passed to the install as written. |
2253
+ | ​ ↳ `draftRevision` | `number` | ❌ | — | — | Expected draft revision for optimistic concurrency. Pass the revision from the last read; the server rejects the save with a conflict if the draft has changed since. Omit to skip the check. |
2254
+ | ​ ↳ `trigger` | `object` | ❌ | — | — | Trigger configuration. Omit to leave the stored trigger unchanged, pass null to clear it, or pass an object to replace it; for an on-demand, triggerless workflow, clear this and pass `manual: true` instead. |
2255
+ | ​ ↳ `connections` | `object` | ❌ | — | — | Map of connection aliases to Zapier connections used by the workflow. Pass `null` to clear an existing binding. |
2256
+ | ​ ↳ `appVersions` | `object` | ❌ | — | — | Map of app keys to pinned app implementation/version used by the workflow. Pass `null` to clear an existing binding. |
2257
+ | ​ ↳ `manual` | `boolean` | ❌ | — | — | Declare this draft on-demand and triggerless. Pass `manual: true` only when omitting `trigger` (or clearing it with `trigger: null`); passing both is a contradiction the API rejects with a 400. Setting a `trigger` later clears a previously stored `manual: true`. |
2258
2258
 
2259
2259
  **Returns:** `Promise<WorkflowDraftItem>`
2260
2260
 
@@ -1988,7 +1988,7 @@ function parseDeprecationDate(value) {
1988
1988
  }
1989
1989
 
1990
1990
  // src/sdk-version.ts
1991
- var SDK_VERSION = (typeof process !== "undefined" && process.env ? "0.102.0" : void 0) || "unknown";
1991
+ var SDK_VERSION = (typeof process !== "undefined" && process.env ? "0.102.2" : void 0) || "unknown";
1992
1992
 
1993
1993
  // src/utils/open-url.ts
1994
1994
  var nodePrefix = "node:";
@@ -2104,6 +2104,7 @@ var PollApprovalResponseSchema = zod.z.object({
2104
2104
  approval_url: zod.z.string().optional()
2105
2105
  });
2106
2106
  var APPROVAL_MAX_POLLING_INTERVAL_MILLISECONDS = 5e3;
2107
+ var EPOCH_THRESHOLD_SECONDS = 1e9;
2107
2108
  function validateSdkPath(path) {
2108
2109
  if (!path.startsWith("/") || path.startsWith("//")) {
2109
2110
  throw new ZapierValidationError(
@@ -2144,11 +2145,12 @@ function parseRateLimitHeaders(response) {
2144
2145
  }
2145
2146
  const reset = response.headers.get("x-ratelimit-reset");
2146
2147
  if (reset) {
2147
- const resetTimestamp = parseInt(reset, 10);
2148
- if (!isNaN(resetTimestamp)) {
2149
- info.resetMs = resetTimestamp * 1e3;
2148
+ const resetValue = parseInt(reset, 10);
2149
+ if (!isNaN(resetValue)) {
2150
+ const isEpoch = resetValue >= EPOCH_THRESHOLD_SECONDS;
2151
+ info.resetMs = isEpoch ? resetValue * 1e3 : Date.now() + resetValue * 1e3;
2150
2152
  if (info.retryAfterMs === void 0) {
2151
- info.retryAfterMs = Math.max(0, info.resetMs - Date.now());
2153
+ info.retryAfterMs = isEpoch ? Math.max(0, info.resetMs - Date.now()) : Math.max(0, resetValue * 1e3);
2152
2154
  }
2153
2155
  }
2154
2156
  }
@@ -2286,11 +2288,14 @@ var ZapierApiClient = class {
2286
2288
  const rateLimitInfo = parseRateLimitHeaders(response);
2287
2289
  const delayMs = rateLimitInfo.retryAfterMs ?? calculateExponentialBackoffMs(retries + 1);
2288
2290
  if (delayMs > this.maxNetworkRetryDelayMilliseconds || retries >= this.maxNetworkRetries) {
2289
- throw new ZapierRateLimitError("Rate limited", {
2290
- statusCode: 429,
2291
- rateLimit: rateLimitInfo,
2292
- retries
2293
- });
2291
+ throw new ZapierRateLimitError(
2292
+ await this.readRateLimitErrorMessage(response),
2293
+ {
2294
+ statusCode: 429,
2295
+ rateLimit: rateLimitInfo,
2296
+ retries
2297
+ }
2298
+ );
2294
2299
  }
2295
2300
  retries++;
2296
2301
  this.emitEvent("api:rate_limit_retry", {
@@ -2725,6 +2730,17 @@ var ZapierApiClient = class {
2725
2730
  }
2726
2731
  return void 0;
2727
2732
  }
2733
+ // Surface the server's explanatory 429 body (e.g. quota details and how
2734
+ // to get a higher limit) instead of a bare "Rate limited". The body is
2735
+ // only consumed on the terminal throw path, never on retries.
2736
+ async readRateLimitErrorMessage(response) {
2737
+ try {
2738
+ const { data } = await this.parseResult(response);
2739
+ return this.extractErrorMessage(data)?.trim() || "Rate limited";
2740
+ } catch {
2741
+ return "Rate limited";
2742
+ }
2743
+ }
2728
2744
  // Helper to parse API error response
2729
2745
  parseErrorResponse(errorInfo) {
2730
2746
  const fallbackMessage = `HTTP ${errorInfo.status}: ${errorInfo.statusText}`;
@@ -1987,7 +1987,7 @@ function parseDeprecationDate(value) {
1987
1987
  }
1988
1988
 
1989
1989
  // src/sdk-version.ts
1990
- var SDK_VERSION = (typeof process !== "undefined" && process.env ? "0.102.0" : void 0) || "unknown";
1990
+ var SDK_VERSION = (typeof process !== "undefined" && process.env ? "0.102.2" : void 0) || "unknown";
1991
1991
 
1992
1992
  // src/utils/open-url.ts
1993
1993
  var nodePrefix = "node:";
@@ -2103,6 +2103,7 @@ var PollApprovalResponseSchema = z.object({
2103
2103
  approval_url: z.string().optional()
2104
2104
  });
2105
2105
  var APPROVAL_MAX_POLLING_INTERVAL_MILLISECONDS = 5e3;
2106
+ var EPOCH_THRESHOLD_SECONDS = 1e9;
2106
2107
  function validateSdkPath(path) {
2107
2108
  if (!path.startsWith("/") || path.startsWith("//")) {
2108
2109
  throw new ZapierValidationError(
@@ -2143,11 +2144,12 @@ function parseRateLimitHeaders(response) {
2143
2144
  }
2144
2145
  const reset = response.headers.get("x-ratelimit-reset");
2145
2146
  if (reset) {
2146
- const resetTimestamp = parseInt(reset, 10);
2147
- if (!isNaN(resetTimestamp)) {
2148
- info.resetMs = resetTimestamp * 1e3;
2147
+ const resetValue = parseInt(reset, 10);
2148
+ if (!isNaN(resetValue)) {
2149
+ const isEpoch = resetValue >= EPOCH_THRESHOLD_SECONDS;
2150
+ info.resetMs = isEpoch ? resetValue * 1e3 : Date.now() + resetValue * 1e3;
2149
2151
  if (info.retryAfterMs === void 0) {
2150
- info.retryAfterMs = Math.max(0, info.resetMs - Date.now());
2152
+ info.retryAfterMs = isEpoch ? Math.max(0, info.resetMs - Date.now()) : Math.max(0, resetValue * 1e3);
2151
2153
  }
2152
2154
  }
2153
2155
  }
@@ -2285,11 +2287,14 @@ var ZapierApiClient = class {
2285
2287
  const rateLimitInfo = parseRateLimitHeaders(response);
2286
2288
  const delayMs = rateLimitInfo.retryAfterMs ?? calculateExponentialBackoffMs(retries + 1);
2287
2289
  if (delayMs > this.maxNetworkRetryDelayMilliseconds || retries >= this.maxNetworkRetries) {
2288
- throw new ZapierRateLimitError("Rate limited", {
2289
- statusCode: 429,
2290
- rateLimit: rateLimitInfo,
2291
- retries
2292
- });
2290
+ throw new ZapierRateLimitError(
2291
+ await this.readRateLimitErrorMessage(response),
2292
+ {
2293
+ statusCode: 429,
2294
+ rateLimit: rateLimitInfo,
2295
+ retries
2296
+ }
2297
+ );
2293
2298
  }
2294
2299
  retries++;
2295
2300
  this.emitEvent("api:rate_limit_retry", {
@@ -2724,6 +2729,17 @@ var ZapierApiClient = class {
2724
2729
  }
2725
2730
  return void 0;
2726
2731
  }
2732
+ // Surface the server's explanatory 429 body (e.g. quota details and how
2733
+ // to get a higher limit) instead of a bare "Rate limited". The body is
2734
+ // only consumed on the terminal throw path, never on retries.
2735
+ async readRateLimitErrorMessage(response) {
2736
+ try {
2737
+ const { data } = await this.parseResult(response);
2738
+ return this.extractErrorMessage(data)?.trim() || "Rate limited";
2739
+ } catch {
2740
+ return "Rate limited";
2741
+ }
2742
+ }
2727
2743
  // Helper to parse API error response
2728
2744
  parseErrorResponse(errorInfo) {
2729
2745
  const fallbackMessage = `HTTP ${errorInfo.status}: ${errorInfo.statusText}`;