@ethisyscore/extension-runtime 1.137.0 → 1.139.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.
@@ -47,13 +47,18 @@ declare const PLUGIN_STORAGE_PATH_MAX_LENGTH = 128;
47
47
  * - `unsupported_type`: the file's extension is not on the allow-list, or the declared type does
48
48
  * not match it (client-side check, or a server 415).
49
49
  * - `timeout`: the upload was too slow or missed the server's deadline (408). Retryable.
50
- * - `busy`: the app's storage lock is held, or the server's upload slots are full (503). Retryable.
50
+ * - `busy`: the server refused the upload for capacity (503). The message says which limit: too
51
+ * many uploads in progress for this user or organisation, all of the server's upload slots taken,
52
+ * or another upload for this app holding the storage lock. NOT retried automatically.
53
+ * - `rate_limited`: the user is over the route's request rate (429, default 60 a minute).
54
+ * {@link PluginStorageUploadError.retryAfterSeconds} carries the server's `Retry-After` when sent.
55
+ * NOT retried automatically.
51
56
  * - `storage_failed`: the store refused the write (500).
52
57
  * - `unsupported`: the active transport has no `uploadToStorage`, i.e. the host predates it.
53
58
  * - `aborted`: the caller's signal fired.
54
59
  * - `unknown`: anything else.
55
60
  */
56
- type PluginStorageUploadErrorReason = "invalid_prefix" | "too_large" | "quota_exceeded" | "unauthorized" | "forbidden" | "unsupported_type" | "timeout" | "busy" | "storage_failed" | "unsupported" | "aborted" | "unknown";
61
+ type PluginStorageUploadErrorReason = "invalid_prefix" | "too_large" | "quota_exceeded" | "unauthorized" | "forbidden" | "unsupported_type" | "timeout" | "busy" | "rate_limited" | "storage_failed" | "unsupported" | "aborted" | "unknown";
57
62
  /**
58
63
  * A failed plugin-storage upload. `message` is the server's own `{ error }` text when the server
59
64
  * answered, otherwise the client-side reason.
@@ -67,11 +72,17 @@ declare class PluginStorageUploadError extends McpToolError {
67
72
  readonly reason: PluginStorageUploadErrorReason;
68
73
  /** HTTP status from the server, when the host surfaced one. */
69
74
  readonly status?: number;
75
+ /**
76
+ * Seconds the server asked the caller to wait (`Retry-After`), on a `rate_limited` failure
77
+ * when the host forwarded the header. Absent otherwise.
78
+ */
79
+ readonly retryAfterSeconds?: number;
70
80
  /** Structural marker for {@link isPluginStorageUploadError}. */
71
81
  readonly isPluginStorageUploadError: true;
72
82
  constructor(message: string, reason: PluginStorageUploadErrorReason, options?: {
73
83
  status?: number;
74
84
  code?: McpErrorCode;
85
+ retryAfterSeconds?: number;
75
86
  });
76
87
  }
77
88
  /** True when `value` is a {@link PluginStorageUploadError}, including one from another bundle. */
@@ -170,6 +181,10 @@ interface UploadToStorageOptions {
170
181
  * {@link PluginStorageUploadError} whose `reason` is safe to branch on and whose `message` is the
171
182
  * server's when the server answered.
172
183
  *
184
+ * An upload is NEVER retried here, on 429, 503 or anything else: it is not idempotent (a retry
185
+ * after a lost response could store the file twice), and a busy or rate-limited server is only
186
+ * made worse by it. The caller decides, using `reason` and `retryAfterSeconds`.
187
+ *
173
188
  * An `ArrayBuffer` argument is transferred to the host and detached; pass a copy to keep it.
174
189
  */
175
190
  declare function uploadFileToPluginStorage(transport: McpTransport, file: File | Blob | ArrayBuffer, options: UploadToStorageOptions): Promise<UploadToStorageResult>;
@@ -47,13 +47,18 @@ declare const PLUGIN_STORAGE_PATH_MAX_LENGTH = 128;
47
47
  * - `unsupported_type`: the file's extension is not on the allow-list, or the declared type does
48
48
  * not match it (client-side check, or a server 415).
49
49
  * - `timeout`: the upload was too slow or missed the server's deadline (408). Retryable.
50
- * - `busy`: the app's storage lock is held, or the server's upload slots are full (503). Retryable.
50
+ * - `busy`: the server refused the upload for capacity (503). The message says which limit: too
51
+ * many uploads in progress for this user or organisation, all of the server's upload slots taken,
52
+ * or another upload for this app holding the storage lock. NOT retried automatically.
53
+ * - `rate_limited`: the user is over the route's request rate (429, default 60 a minute).
54
+ * {@link PluginStorageUploadError.retryAfterSeconds} carries the server's `Retry-After` when sent.
55
+ * NOT retried automatically.
51
56
  * - `storage_failed`: the store refused the write (500).
52
57
  * - `unsupported`: the active transport has no `uploadToStorage`, i.e. the host predates it.
53
58
  * - `aborted`: the caller's signal fired.
54
59
  * - `unknown`: anything else.
55
60
  */
56
- type PluginStorageUploadErrorReason = "invalid_prefix" | "too_large" | "quota_exceeded" | "unauthorized" | "forbidden" | "unsupported_type" | "timeout" | "busy" | "storage_failed" | "unsupported" | "aborted" | "unknown";
61
+ type PluginStorageUploadErrorReason = "invalid_prefix" | "too_large" | "quota_exceeded" | "unauthorized" | "forbidden" | "unsupported_type" | "timeout" | "busy" | "rate_limited" | "storage_failed" | "unsupported" | "aborted" | "unknown";
57
62
  /**
58
63
  * A failed plugin-storage upload. `message` is the server's own `{ error }` text when the server
59
64
  * answered, otherwise the client-side reason.
@@ -67,11 +72,17 @@ declare class PluginStorageUploadError extends McpToolError {
67
72
  readonly reason: PluginStorageUploadErrorReason;
68
73
  /** HTTP status from the server, when the host surfaced one. */
69
74
  readonly status?: number;
75
+ /**
76
+ * Seconds the server asked the caller to wait (`Retry-After`), on a `rate_limited` failure
77
+ * when the host forwarded the header. Absent otherwise.
78
+ */
79
+ readonly retryAfterSeconds?: number;
70
80
  /** Structural marker for {@link isPluginStorageUploadError}. */
71
81
  readonly isPluginStorageUploadError: true;
72
82
  constructor(message: string, reason: PluginStorageUploadErrorReason, options?: {
73
83
  status?: number;
74
84
  code?: McpErrorCode;
85
+ retryAfterSeconds?: number;
75
86
  });
76
87
  }
77
88
  /** True when `value` is a {@link PluginStorageUploadError}, including one from another bundle. */
@@ -170,6 +181,10 @@ interface UploadToStorageOptions {
170
181
  * {@link PluginStorageUploadError} whose `reason` is safe to branch on and whose `message` is the
171
182
  * server's when the server answered.
172
183
  *
184
+ * An upload is NEVER retried here, on 429, 503 or anything else: it is not idempotent (a retry
185
+ * after a lost response could store the file twice), and a busy or rate-limited server is only
186
+ * made worse by it. The caller decides, using `reason` and `retryAfterSeconds`.
187
+ *
173
188
  * An `ArrayBuffer` argument is transferred to the host and detached; pass a copy to keep it.
174
189
  */
175
190
  declare function uploadFileToPluginStorage(transport: McpTransport, file: File | Blob | ArrayBuffer, options: UploadToStorageOptions): Promise<UploadToStorageResult>;
@@ -108,6 +108,11 @@ var PluginStorageUploadError = class extends McpToolError {
108
108
  reason;
109
109
  /** HTTP status from the server, when the host surfaced one. */
110
110
  status;
111
+ /**
112
+ * Seconds the server asked the caller to wait (`Retry-After`), on a `rate_limited` failure
113
+ * when the host forwarded the header. Absent otherwise.
114
+ */
115
+ retryAfterSeconds;
111
116
  /** Structural marker for {@link isPluginStorageUploadError}. */
112
117
  isPluginStorageUploadError = true;
113
118
  constructor(message, reason, options = {}) {
@@ -115,6 +120,9 @@ var PluginStorageUploadError = class extends McpToolError {
115
120
  this.name = "PluginStorageUploadError";
116
121
  this.reason = reason;
117
122
  this.status = options.status;
123
+ if (options.retryAfterSeconds !== void 0) {
124
+ this.retryAfterSeconds = options.retryAfterSeconds;
125
+ }
118
126
  }
119
127
  };
120
128
  function isPluginStorageUploadError(value) {
@@ -134,6 +142,8 @@ function pluginStorageUploadReasonFromStatus(status) {
134
142
  return "timeout";
135
143
  case 415:
136
144
  return "unsupported_type";
145
+ case 429:
146
+ return "rate_limited";
137
147
  case 503:
138
148
  return "busy";
139
149
  case 507:
@@ -144,6 +154,20 @@ function pluginStorageUploadReasonFromStatus(status) {
144
154
  return "unknown";
145
155
  }
146
156
  }
157
+ function parsePluginStorageRetryAfter(raw, nowMs = Date.now()) {
158
+ if (typeof raw === "number") {
159
+ return Number.isFinite(raw) && raw >= 0 ? Math.ceil(raw) : void 0;
160
+ }
161
+ if (typeof raw !== "string" || raw.trim().length === 0) {
162
+ return void 0;
163
+ }
164
+ const text = raw.trim();
165
+ if (/^\d+$/.test(text)) {
166
+ return Number(text);
167
+ }
168
+ const at = /[A-Za-z]/.test(text) ? Date.parse(text) : Number.NaN;
169
+ return Number.isNaN(at) ? void 0 : Math.max(0, Math.ceil((at - nowMs) / 1e3));
170
+ }
147
171
  function toPluginStorageUploadError(err) {
148
172
  if (isPluginStorageUploadError(err)) {
149
173
  return err;
@@ -152,7 +176,10 @@ function toPluginStorageUploadError(err) {
152
176
  const candidate = err !== null && typeof err === "object" ? err : {};
153
177
  const status = typeof candidate.status === "number" ? candidate.status : typeof candidate.statusCode === "number" ? candidate.statusCode : void 0;
154
178
  if (status !== void 0) {
155
- return new PluginStorageUploadError(message, pluginStorageUploadReasonFromStatus(status), { status });
179
+ return new PluginStorageUploadError(message, pluginStorageUploadReasonFromStatus(status), {
180
+ status,
181
+ retryAfterSeconds: parsePluginStorageRetryAfter(candidate.retryAfterSeconds)
182
+ });
156
183
  }
157
184
  if (candidate.name === "AbortError") {
158
185
  return new PluginStorageUploadError(message, "aborted", { code: "timeout" });
@@ -328,6 +355,8 @@ function codeForReason(reason, status) {
328
355
  return "forbidden";
329
356
  case "busy":
330
357
  return "unavailable";
358
+ case "rate_limited":
359
+ return "rate_limited";
331
360
  case "aborted":
332
361
  return "timeout";
333
362
  default:
@@ -950,7 +979,7 @@ function createPortMcpTransport(port, options = {}) {
950
979
  resolver.reject(new PluginStorageUploadError(
951
980
  message,
952
981
  status !== void 0 ? pluginStorageUploadReasonFromStatus(status) : "unknown",
953
- { status, code }
982
+ { status, code, retryAfterSeconds: parsePluginStorageRetryAfter(reply.retryAfterSeconds) }
954
983
  ));
955
984
  return;
956
985
  }