@dropthis/cli 0.23.0 → 0.24.1

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.
@@ -1,3 +1,5 @@
1
+ <p align="center"><img src="https://dropthis.app/icon-512.png" width="76" height="76" alt="dropthis" /></p>
2
+
1
3
  # @dropthis/node
2
4
 
3
5
  Official Node.js SDK for [dropthis](https://dropthis.app) -- the publish layer between AI and the internet. One API call in, one URL out.
@@ -275,6 +277,15 @@ if (result.error) {
275
277
 
276
278
  Local input validation errors (e.g. `file_not_found`) are also returned as `{ error: { code: "file_not_found", ... } }` rather than thrown -- except for `prepare()`, which throws `PublishInputError`.
277
279
 
280
+ `error.retryable` tells an agent whether retrying is worthwhile. The SDK marks its own
281
+ transient failures `retryable: true` only when retrying is safe: a `GET` `timeout` /
282
+ `network_error`, any request you sent with an `idempotencyKey`, or a signed-upload `5xx`.
283
+ A bare (non-idempotent) `POST`/`PATCH`/`DELETE` timeout is left unmarked because the
284
+ server may have applied it before the connection dropped. Server-side errors carry
285
+ whatever `retryable` the API returns (the SDK never overrides it). `publish()` and
286
+ `updateContent()` are always safe to retry — each derives a stable idempotency key, so a
287
+ retried publish never creates a duplicate drop.
288
+
278
289
  ## Configuration
279
290
 
280
291
  ```typescript
@@ -313,14 +324,21 @@ List results support auto-pagination:
313
324
 
314
325
  ```typescript
315
326
  const page = await dropthis.drops.list();
316
- const allDrops = await page.data.autoPagingToArray({ limit: 100 });
317
-
318
- // Or iterate
319
- for await (const drop of page.data) {
320
- console.log(drop.url);
327
+ const all = await page.data.autoPagingToArray({ limit: 100 });
328
+ if (all.error) {
329
+ // a later page failed to fetch — inspect all.error.code / statusCode / requestId
330
+ } else {
331
+ for (const drop of all.data) console.log(drop.url);
321
332
  }
322
333
  ```
323
334
 
335
+ Like every call in this SDK, `autoPagingToArray()` **never throws** for an API error — it
336
+ returns the same `{ data, error }` result shape. On success you get `{ data: items, error: null }`;
337
+ if fetching a later page fails you get `{ data: null, error }` carrying the underlying
338
+ `code` / `statusCode` / `retryable` / `requestId`. So you can never be handed a silently
339
+ truncated list, and you never have to wrap pagination in `try/catch`. (For manual control,
340
+ loop on `dropthis.drops.list({ cursor })` and check each result's `.error`.)
341
+
324
342
  > To change a drop's content, use `client.drops.updateContent(dropId, newInput)`. `drops.updateSettings()` is for settings only (title, visibility, password, noindex, expiresAt, metadata).
325
343
 
326
344
  ### deployments
@@ -352,6 +370,8 @@ await dropthis.auth.verifyEmailOtp({ email: "you@example.com", code: "123456" })
352
370
  await dropthis.auth.logout(); // 204 No Content — data is null
353
371
  ```
354
372
 
373
+ > On a failed verify the error `code` is `otp_expired` (no active code — request a new one) or `otp_invalid` (wrong digits — re-check, don't auto-resend). 401 on either.
374
+
355
375
  ### apiKeys
356
376
 
357
377
  ```typescript
@@ -464,11 +484,20 @@ import type {
464
484
  PublishInput,
465
485
  PublishFileInput,
466
486
  ListPage,
487
+ KeyType,
488
+ RevokeImpact,
467
489
  CreateUploadSessionRequest,
468
490
  CreateUploadSessionResponse,
469
491
  } from "@dropthis/node";
470
492
  ```
471
493
 
494
+ `PublishInputError` (thrown only for programmer errors when building a publish input,
495
+ never for API failures) is a runtime export:
496
+
497
+ ```typescript
498
+ import { PublishInputError } from "@dropthis/node";
499
+ ```
500
+
472
501
  ## Agent skills
473
502
 
474
503
  For AI coding agents (Cursor, Claude Code, Windsurf, etc.), install the [dropthis-skills](https://github.com/dropthis-dev/dropthis-skills) package:
@@ -77,6 +77,10 @@ type DropResponse = {
77
77
  noindex: boolean;
78
78
  passwordProtected: boolean;
79
79
  metadata: Record<string, unknown>;
80
+ /** When the drop was last updated (content or settings), ISO 8601. */
81
+ updatedAt: string;
82
+ /** Origin that created the drop (e.g. "api", "cli", "mcp"); null/omitted when unattributed. */
83
+ source?: string | null;
80
84
  object: string;
81
85
  accessible: boolean;
82
86
  persistent: boolean;
@@ -131,8 +135,7 @@ type ListPage<T> = {
131
135
  nextCursor: string | null;
132
136
  autoPagingToArray(options?: {
133
137
  limit?: number;
134
- }): Promise<T[]>;
135
- [Symbol.asyncIterator](): AsyncIterableIterator<T>;
138
+ }): Promise<DropthisResult<T[]>>;
136
139
  };
137
140
  type Action = {
138
141
  code: string;
@@ -142,7 +145,8 @@ type Action = {
142
145
  message: string;
143
146
  };
144
147
  type EmailOtpResponse = {
145
- ok: true;
148
+ /** Always `true` on success; optional because the server defaults it. */
149
+ ok?: true;
146
150
  expiresIn: number;
147
151
  nextAction?: Action | null;
148
152
  };
@@ -152,12 +156,35 @@ type SessionResponse = {
152
156
  accountId: string;
153
157
  isNewAccount: boolean;
154
158
  expiresIn: number;
159
+ /**
160
+ * Rotating refresh token for a console browser session. Present when a session is
161
+ * started (email/verify, refresh); `null`/omitted for non-session token issuance.
162
+ */
163
+ refreshToken?: string | null;
155
164
  };
165
+ /**
166
+ * Why an API key exists. `standard` keys are created by the user (CLI/SDK/manual);
167
+ * `mcp_oauth` keys are minted per connected client by the MCP OAuth flow and carry a
168
+ * more generous quota.
169
+ */
170
+ type KeyType = "standard" | "mcp_oauth";
171
+ /** What breaks when a key is revoked. */
172
+ type RevokeImpact = "disconnects_app" | "breaks_automation";
156
173
  type ApiKeyResponse = {
157
174
  object: "api_key";
158
175
  id: string;
159
176
  keyLast4: string;
160
177
  label: string;
178
+ /** Human-facing credential name: the user's label for `standard` keys, the connected client name for `mcp_oauth` keys. */
179
+ appName: string;
180
+ /** Why the key exists (drives quota accounting). */
181
+ keyType: KeyType;
182
+ /** Scopes granted to this key. */
183
+ scopes: string[];
184
+ /** When the key was last used to authenticate, ISO 8601; null/omitted if never used. */
185
+ lastUsedAt?: string | null;
186
+ /** What breaks if this key is revoked. */
187
+ revokeImpact: RevokeImpact;
161
188
  createdAt: string;
162
189
  };
163
190
  type ApiKeyCreatedResponse = ApiKeyResponse & {
@@ -252,9 +279,17 @@ type CreateUploadSessionFileResponse = {
252
279
  type UploadSessionFileResponse = {
253
280
  fileId: string;
254
281
  path: string;
255
- contentType: string;
256
- sizeBytes: number;
282
+ /** Declared MIME type; `null`/omitted for remote-fetch files until ingested. */
283
+ contentType?: string | null;
284
+ /** Declared byte size; `null`/omitted for remote-fetch files until ingested. */
285
+ sizeBytes?: number | null;
257
286
  objectKey: string;
287
+ /** How the file enters staging: "client_put" (client PUTs bytes) or "remote_fetch" (server fetches sourceUrl). */
288
+ origin: "client_put" | "remote_fetch";
289
+ /** Ingest lifecycle state for remote-fetch files (e.g. "pending" | "fetching" | "fetched" | "failed"). */
290
+ state: string;
291
+ /** Public http(s) URL the server fetches into staging; `null`/omitted for client-put files. */
292
+ sourceUrl?: string | null;
258
293
  verified: boolean;
259
294
  };
260
295
  type UploadSessionResponse = {
@@ -696,17 +731,29 @@ declare class CursorPage<T> implements ListPage<T> {
696
731
  readonly data: T[];
697
732
  readonly hasMore: boolean;
698
733
  readonly nextCursor: string | null;
734
+ /** Response headers from the fetch that produced this page. */
735
+ readonly headers: Record<string, string>;
699
736
  private readonly fetchNextPage;
700
737
  constructor(input: {
701
738
  data: T[];
702
739
  hasMore: boolean;
703
740
  nextCursor: string | null;
741
+ headers?: Record<string, string>;
704
742
  fetchNextPage?: (() => Promise<DropthisResult<CursorPage<T>>>) | undefined;
705
743
  });
744
+ /**
745
+ * Collect items across every page into a single array.
746
+ *
747
+ * No-throw, consistent with the rest of the SDK's {@link DropthisResult}
748
+ * contract: returns `{ data: items, error: null }` on success, or
749
+ * `{ data: null, error }` if fetching a later page fails — never a thrown
750
+ * exception, and never a silently truncated list. Inspect `.error`
751
+ * (`code`/`statusCode`/`retryable`/`requestId`) exactly as you would for any
752
+ * other call. Pass `limit` to stop once that many items are collected.
753
+ */
706
754
  autoPagingToArray(options?: {
707
755
  limit?: number;
708
- }): Promise<T[]>;
709
- [Symbol.asyncIterator](): AsyncIterableIterator<T>;
756
+ }): Promise<DropthisResult<T[]>>;
710
757
  }
711
758
 
712
759
  /**
@@ -794,4 +841,4 @@ declare class DropsResource<TInput = PublishInput> {
794
841
  delete(dropId: string): Promise<DropthisResult<null>>;
795
842
  }
796
843
 
797
- export { type AccountLimits as A, type PreparedUploadFile as B, type CreateUploadSessionRequest as C, type DeploymentContentFile as D, type EmailOtpResponse as E, type PublishFileInput as F, type GetContentOptions as G, type PublishInput as H, type InMemoryPublishInput as I, type PublishOptions as J, type RequestOptions as K, type Limitations as L, type SessionResponse as M, type NextHint as N, Transport as O, type PrepareOptions as P, type UploadManifestFile as Q, type RequestControls as R, SHARED_POOL as S, type TierInfo as T, type UpdateContentOptions as U, type UploadSessionFileResponse as V, type UploadSessionResponse as W, type UploadTarget as X, AccountResource as a, type AccountResponse as b, type ActionResolve as c, ApiKeysResource as d, type CreateUploadSessionResponse as e, CursorPage as f, type DeploymentContentManifest as g, DeploymentsResource as h, type DnsRecord as i, type DomainDeletedResponse as j, type DomainListResponse as k, type DomainResponse as l, DomainsResource as m, type DropAction as n, type DropContentFile as o, type DropDeploymentResponse as p, type DropOptions as q, type DropResponse as r, DropsResource as s, type DropthisClientOptions as t, type DropthisErrorResponse as u, type DropthisResult as v, type ListDeploymentsParams as w, type ListDeploymentsResponse as x, type ListPage as y, type PreparedPublishRequest as z };
844
+ export { type AccountLimits as A, type PreparedUploadFile as B, type CreateUploadSessionRequest as C, type DeploymentContentFile as D, type EmailOtpResponse as E, type PublishFileInput as F, type GetContentOptions as G, type PublishInput as H, type InMemoryPublishInput as I, type PublishOptions as J, type KeyType as K, type Limitations as L, type RequestOptions as M, type NextHint as N, type RevokeImpact as O, type PrepareOptions as P, type SessionResponse as Q, type RequestControls as R, SHARED_POOL as S, type TierInfo as T, Transport as U, type UpdateContentOptions as V, type UploadManifestFile as W, type UploadSessionFileResponse as X, type UploadSessionResponse as Y, type UploadTarget as Z, AccountResource as a, type AccountResponse as b, type ActionResolve as c, ApiKeysResource as d, type CreateUploadSessionResponse as e, CursorPage as f, type DeploymentContentManifest as g, DeploymentsResource as h, type DnsRecord as i, type DomainDeletedResponse as j, type DomainListResponse as k, type DomainResponse as l, DomainsResource as m, type DropAction as n, type DropContentFile as o, type DropDeploymentResponse as p, type DropOptions as q, type DropResponse as r, DropsResource as s, type DropthisClientOptions as t, type DropthisErrorResponse as u, type DropthisResult as v, type ListDeploymentsParams as w, type ListDeploymentsResponse as x, type ListPage as y, type PreparedPublishRequest as z };
@@ -77,6 +77,10 @@ type DropResponse = {
77
77
  noindex: boolean;
78
78
  passwordProtected: boolean;
79
79
  metadata: Record<string, unknown>;
80
+ /** When the drop was last updated (content or settings), ISO 8601. */
81
+ updatedAt: string;
82
+ /** Origin that created the drop (e.g. "api", "cli", "mcp"); null/omitted when unattributed. */
83
+ source?: string | null;
80
84
  object: string;
81
85
  accessible: boolean;
82
86
  persistent: boolean;
@@ -131,8 +135,7 @@ type ListPage<T> = {
131
135
  nextCursor: string | null;
132
136
  autoPagingToArray(options?: {
133
137
  limit?: number;
134
- }): Promise<T[]>;
135
- [Symbol.asyncIterator](): AsyncIterableIterator<T>;
138
+ }): Promise<DropthisResult<T[]>>;
136
139
  };
137
140
  type Action = {
138
141
  code: string;
@@ -142,7 +145,8 @@ type Action = {
142
145
  message: string;
143
146
  };
144
147
  type EmailOtpResponse = {
145
- ok: true;
148
+ /** Always `true` on success; optional because the server defaults it. */
149
+ ok?: true;
146
150
  expiresIn: number;
147
151
  nextAction?: Action | null;
148
152
  };
@@ -152,12 +156,35 @@ type SessionResponse = {
152
156
  accountId: string;
153
157
  isNewAccount: boolean;
154
158
  expiresIn: number;
159
+ /**
160
+ * Rotating refresh token for a console browser session. Present when a session is
161
+ * started (email/verify, refresh); `null`/omitted for non-session token issuance.
162
+ */
163
+ refreshToken?: string | null;
155
164
  };
165
+ /**
166
+ * Why an API key exists. `standard` keys are created by the user (CLI/SDK/manual);
167
+ * `mcp_oauth` keys are minted per connected client by the MCP OAuth flow and carry a
168
+ * more generous quota.
169
+ */
170
+ type KeyType = "standard" | "mcp_oauth";
171
+ /** What breaks when a key is revoked. */
172
+ type RevokeImpact = "disconnects_app" | "breaks_automation";
156
173
  type ApiKeyResponse = {
157
174
  object: "api_key";
158
175
  id: string;
159
176
  keyLast4: string;
160
177
  label: string;
178
+ /** Human-facing credential name: the user's label for `standard` keys, the connected client name for `mcp_oauth` keys. */
179
+ appName: string;
180
+ /** Why the key exists (drives quota accounting). */
181
+ keyType: KeyType;
182
+ /** Scopes granted to this key. */
183
+ scopes: string[];
184
+ /** When the key was last used to authenticate, ISO 8601; null/omitted if never used. */
185
+ lastUsedAt?: string | null;
186
+ /** What breaks if this key is revoked. */
187
+ revokeImpact: RevokeImpact;
161
188
  createdAt: string;
162
189
  };
163
190
  type ApiKeyCreatedResponse = ApiKeyResponse & {
@@ -252,9 +279,17 @@ type CreateUploadSessionFileResponse = {
252
279
  type UploadSessionFileResponse = {
253
280
  fileId: string;
254
281
  path: string;
255
- contentType: string;
256
- sizeBytes: number;
282
+ /** Declared MIME type; `null`/omitted for remote-fetch files until ingested. */
283
+ contentType?: string | null;
284
+ /** Declared byte size; `null`/omitted for remote-fetch files until ingested. */
285
+ sizeBytes?: number | null;
257
286
  objectKey: string;
287
+ /** How the file enters staging: "client_put" (client PUTs bytes) or "remote_fetch" (server fetches sourceUrl). */
288
+ origin: "client_put" | "remote_fetch";
289
+ /** Ingest lifecycle state for remote-fetch files (e.g. "pending" | "fetching" | "fetched" | "failed"). */
290
+ state: string;
291
+ /** Public http(s) URL the server fetches into staging; `null`/omitted for client-put files. */
292
+ sourceUrl?: string | null;
258
293
  verified: boolean;
259
294
  };
260
295
  type UploadSessionResponse = {
@@ -696,17 +731,29 @@ declare class CursorPage<T> implements ListPage<T> {
696
731
  readonly data: T[];
697
732
  readonly hasMore: boolean;
698
733
  readonly nextCursor: string | null;
734
+ /** Response headers from the fetch that produced this page. */
735
+ readonly headers: Record<string, string>;
699
736
  private readonly fetchNextPage;
700
737
  constructor(input: {
701
738
  data: T[];
702
739
  hasMore: boolean;
703
740
  nextCursor: string | null;
741
+ headers?: Record<string, string>;
704
742
  fetchNextPage?: (() => Promise<DropthisResult<CursorPage<T>>>) | undefined;
705
743
  });
744
+ /**
745
+ * Collect items across every page into a single array.
746
+ *
747
+ * No-throw, consistent with the rest of the SDK's {@link DropthisResult}
748
+ * contract: returns `{ data: items, error: null }` on success, or
749
+ * `{ data: null, error }` if fetching a later page fails — never a thrown
750
+ * exception, and never a silently truncated list. Inspect `.error`
751
+ * (`code`/`statusCode`/`retryable`/`requestId`) exactly as you would for any
752
+ * other call. Pass `limit` to stop once that many items are collected.
753
+ */
706
754
  autoPagingToArray(options?: {
707
755
  limit?: number;
708
- }): Promise<T[]>;
709
- [Symbol.asyncIterator](): AsyncIterableIterator<T>;
756
+ }): Promise<DropthisResult<T[]>>;
710
757
  }
711
758
 
712
759
  /**
@@ -794,4 +841,4 @@ declare class DropsResource<TInput = PublishInput> {
794
841
  delete(dropId: string): Promise<DropthisResult<null>>;
795
842
  }
796
843
 
797
- export { type AccountLimits as A, type PreparedUploadFile as B, type CreateUploadSessionRequest as C, type DeploymentContentFile as D, type EmailOtpResponse as E, type PublishFileInput as F, type GetContentOptions as G, type PublishInput as H, type InMemoryPublishInput as I, type PublishOptions as J, type RequestOptions as K, type Limitations as L, type SessionResponse as M, type NextHint as N, Transport as O, type PrepareOptions as P, type UploadManifestFile as Q, type RequestControls as R, SHARED_POOL as S, type TierInfo as T, type UpdateContentOptions as U, type UploadSessionFileResponse as V, type UploadSessionResponse as W, type UploadTarget as X, AccountResource as a, type AccountResponse as b, type ActionResolve as c, ApiKeysResource as d, type CreateUploadSessionResponse as e, CursorPage as f, type DeploymentContentManifest as g, DeploymentsResource as h, type DnsRecord as i, type DomainDeletedResponse as j, type DomainListResponse as k, type DomainResponse as l, DomainsResource as m, type DropAction as n, type DropContentFile as o, type DropDeploymentResponse as p, type DropOptions as q, type DropResponse as r, DropsResource as s, type DropthisClientOptions as t, type DropthisErrorResponse as u, type DropthisResult as v, type ListDeploymentsParams as w, type ListDeploymentsResponse as x, type ListPage as y, type PreparedPublishRequest as z };
844
+ export { type AccountLimits as A, type PreparedUploadFile as B, type CreateUploadSessionRequest as C, type DeploymentContentFile as D, type EmailOtpResponse as E, type PublishFileInput as F, type GetContentOptions as G, type PublishInput as H, type InMemoryPublishInput as I, type PublishOptions as J, type KeyType as K, type Limitations as L, type RequestOptions as M, type NextHint as N, type RevokeImpact as O, type PrepareOptions as P, type SessionResponse as Q, type RequestControls as R, SHARED_POOL as S, type TierInfo as T, Transport as U, type UpdateContentOptions as V, type UploadManifestFile as W, type UploadSessionFileResponse as X, type UploadSessionResponse as Y, type UploadTarget as Z, AccountResource as a, type AccountResponse as b, type ActionResolve as c, ApiKeysResource as d, type CreateUploadSessionResponse as e, CursorPage as f, type DeploymentContentManifest as g, DeploymentsResource as h, type DnsRecord as i, type DomainDeletedResponse as j, type DomainListResponse as k, type DomainResponse as l, DomainsResource as m, type DropAction as n, type DropContentFile as o, type DropDeploymentResponse as p, type DropOptions as q, type DropResponse as r, DropsResource as s, type DropthisClientOptions as t, type DropthisErrorResponse as u, type DropthisResult as v, type ListDeploymentsParams as w, type ListDeploymentsResponse as x, type ListPage as y, type PreparedPublishRequest as z };
@@ -636,30 +636,50 @@ var CursorPage = class {
636
636
  data;
637
637
  hasMore;
638
638
  nextCursor;
639
+ /** Response headers from the fetch that produced this page. */
640
+ headers;
639
641
  fetchNextPage;
640
642
  constructor(input) {
641
643
  this.data = input.data;
642
644
  this.hasMore = input.hasMore;
643
645
  this.nextCursor = input.nextCursor;
646
+ this.headers = input.headers ?? {};
644
647
  this.fetchNextPage = input.fetchNextPage;
645
648
  }
649
+ /**
650
+ * Collect items across every page into a single array.
651
+ *
652
+ * No-throw, consistent with the rest of the SDK's {@link DropthisResult}
653
+ * contract: returns `{ data: items, error: null }` on success, or
654
+ * `{ data: null, error }` if fetching a later page fails — never a thrown
655
+ * exception, and never a silently truncated list. Inspect `.error`
656
+ * (`code`/`statusCode`/`retryable`/`requestId`) exactly as you would for any
657
+ * other call. Pass `limit` to stop once that many items are collected.
658
+ */
646
659
  async autoPagingToArray(options = {}) {
647
- const items = [];
648
- for await (const item of this) {
649
- items.push(item);
650
- if (options.limit !== void 0 && items.length >= options.limit) break;
660
+ const { limit } = options;
661
+ if (limit !== void 0 && limit <= 0) {
662
+ return { data: [], error: null, headers: this.headers };
651
663
  }
652
- return items;
653
- }
654
- async *[Symbol.asyncIterator]() {
655
- for (const item of this.data) yield item;
664
+ const items = [];
656
665
  let current = this;
657
- while (current.hasMore && current.fetchNextPage) {
666
+ let lastHeaders = this.headers;
667
+ while (current) {
668
+ for (const item of current.data) {
669
+ items.push(item);
670
+ if (limit !== void 0 && items.length >= limit) {
671
+ return { data: items, error: null, headers: lastHeaders };
672
+ }
673
+ }
674
+ if (!current.hasMore || !current.fetchNextPage) break;
658
675
  const next = await current.fetchNextPage();
659
- if (next.error) break;
676
+ if (next.error) {
677
+ return { data: null, error: next.error, headers: next.headers };
678
+ }
679
+ lastHeaders = next.headers;
660
680
  current = next.data;
661
- for (const item of current.data) yield item;
662
681
  }
682
+ return { data: items, error: null, headers: lastHeaders };
663
683
  }
664
684
  };
665
685
 
@@ -728,6 +748,7 @@ var DropsResource = class {
728
748
  data: result.data.drops,
729
749
  nextCursor: result.data.nextCursor,
730
750
  hasMore: result.data.nextCursor !== null,
751
+ headers: result.headers,
731
752
  fetchNextPage: result.data.nextCursor ? () => this.list({ ...params, cursor: result.data.nextCursor }) : void 0
732
753
  });
733
754
  return { data: page, error: null, headers: result.headers };
@@ -871,7 +892,7 @@ function toSnakeCase(value) {
871
892
 
872
893
  // src/transport.ts
873
894
  var DEFAULT_BASE_URL = "https://api.dropthis.app";
874
- var SDK_VERSION = "0.20.0";
895
+ var SDK_VERSION = "0.22.0";
875
896
  var Transport = class {
876
897
  apiKey;
877
898
  baseUrl;
@@ -910,7 +931,10 @@ var Transport = class {
910
931
  `signed_upload_${response.status}`,
911
932
  text || response.statusText,
912
933
  response.status,
913
- { headers: responseHeaders }
934
+ // A 5xx from object storage is a transient upstream failure — safe to
935
+ // re-PUT. A 4xx (e.g. 403 expired/denied signed URL) won't fix itself on
936
+ // a blind retry, so leave it unmarked.
937
+ { headers: responseHeaders, retryable: response.status >= 500 }
914
938
  );
915
939
  }
916
940
  return {
@@ -925,7 +949,8 @@ var Transport = class {
925
949
  return createErrorResult(
926
950
  error instanceof Error && error.name === "AbortError" ? "timeout" : "network_error",
927
951
  message,
928
- null
952
+ null,
953
+ { retryable: true }
929
954
  );
930
955
  } finally {
931
956
  clearTimeout(timeout);
@@ -982,7 +1007,8 @@ var Transport = class {
982
1007
  return createErrorResult(
983
1008
  error instanceof Error && error.name === "AbortError" ? "timeout" : "network_error",
984
1009
  message,
985
- null
1010
+ null,
1011
+ { retryable: true }
986
1012
  );
987
1013
  } finally {
988
1014
  clearTimeout(timeout);
@@ -1043,10 +1069,12 @@ var Transport = class {
1043
1069
  };
1044
1070
  } catch (error) {
1045
1071
  const message = error instanceof Error && error.name === "AbortError" ? "Request timed out" : error instanceof Error ? error.message : "Network error";
1072
+ const retrySafe = method.toUpperCase() === "GET" || options.idempotencyKey !== void 0;
1046
1073
  return createErrorResult(
1047
1074
  error instanceof Error && error.name === "AbortError" ? "timeout" : "network_error",
1048
1075
  message,
1049
- null
1076
+ null,
1077
+ retrySafe ? { retryable: true } : {}
1050
1078
  );
1051
1079
  } finally {
1052
1080
  clearTimeout(timeout);