@relayfile/sdk 0.10.51 → 0.10.52
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/dist/client.d.ts +7 -0
- package/dist/client.js +71 -5
- package/package.json +6 -6
package/dist/client.d.ts
CHANGED
|
@@ -178,6 +178,13 @@ export declare class RelayFileClient {
|
|
|
178
178
|
private shouldRetryStatus;
|
|
179
179
|
private shouldRetryError;
|
|
180
180
|
private computeRetryDelayMs;
|
|
181
|
+
/**
|
|
182
|
+
* Extract a server-advertised retry delay from a parsed 429 error body.
|
|
183
|
+
* Relayfile advertises backpressure delays as `details.retryAfterSeconds`
|
|
184
|
+
* (in seconds) on `workspace_busy` / `queue_full` responses. Returns
|
|
185
|
+
* milliseconds, or null when absent/malformed.
|
|
186
|
+
*/
|
|
187
|
+
private parseRetryAfterSecondsFromBody;
|
|
181
188
|
private parseRetryAfterMs;
|
|
182
189
|
private sleep;
|
|
183
190
|
private readPayload;
|
package/dist/client.js
CHANGED
|
@@ -8,6 +8,15 @@ const DEFAULT_RETRY_OPTIONS = {
|
|
|
8
8
|
maxDelayMs: 2000,
|
|
9
9
|
jitterRatio: 0.2
|
|
10
10
|
};
|
|
11
|
+
/**
|
|
12
|
+
* Hard ceiling for a *server-advertised* retry delay (a 429 body's
|
|
13
|
+
* `details.retryAfterSeconds`). `maxDelayMs` bounds our OWN exponential
|
|
14
|
+
* backoff; an explicit server instruction ("retry after N seconds") is honored
|
|
15
|
+
* above that cap — truncating it just retries into the same overloaded resource
|
|
16
|
+
* and burns the retry budget — but is still bounded here so a pathological or
|
|
17
|
+
* hostile value cannot stall the client indefinitely.
|
|
18
|
+
*/
|
|
19
|
+
const RETRY_AFTER_MAX_MS = 30_000;
|
|
11
20
|
const DEFAULT_CHANGE_COALESCE_MS = 200;
|
|
12
21
|
const DEFAULT_CHANGE_LOG_RETENTION_MS = 7 * 24 * 60 * 60 * 1000;
|
|
13
22
|
const DEFAULT_CHANGE_LOG_MAX_ENTRIES = 10_000;
|
|
@@ -255,7 +264,9 @@ class RelayFileWebSocketConnection {
|
|
|
255
264
|
}
|
|
256
265
|
});
|
|
257
266
|
socket.addEventListener("error", (event) => {
|
|
258
|
-
const errorEvent =
|
|
267
|
+
const errorEvent = typeof ErrorEvent !== "undefined" && event instanceof ErrorEvent && event.error instanceof Error
|
|
268
|
+
? event.error
|
|
269
|
+
: event;
|
|
259
270
|
for (const handler of this.handlers.error) {
|
|
260
271
|
handler(errorEvent);
|
|
261
272
|
}
|
|
@@ -777,7 +788,14 @@ function getWorkspaceIdFromToken(token) {
|
|
|
777
788
|
}
|
|
778
789
|
try {
|
|
779
790
|
const parsed = JSON.parse(decodeBase64Url(parts[1] ?? ""));
|
|
780
|
-
|
|
791
|
+
// Prefer the canonical `workspace_id` claim; fall back to the `wks` claim
|
|
792
|
+
// minted delegated tokens (relay_pa_*) actually carry, then `workspace`.
|
|
793
|
+
for (const candidate of [parsed.workspace_id, parsed.wks, parsed.workspace]) {
|
|
794
|
+
if (typeof candidate === "string" && candidate.length > 0) {
|
|
795
|
+
return candidate;
|
|
796
|
+
}
|
|
797
|
+
}
|
|
798
|
+
return undefined;
|
|
781
799
|
}
|
|
782
800
|
catch {
|
|
783
801
|
return undefined;
|
|
@@ -790,7 +808,15 @@ function getAgentIdFromToken(token) {
|
|
|
790
808
|
}
|
|
791
809
|
try {
|
|
792
810
|
const parsed = JSON.parse(decodeBase64Url(parts[1] ?? ""));
|
|
793
|
-
|
|
811
|
+
if (typeof parsed.agent_name === "string" && parsed.agent_name.length > 0) {
|
|
812
|
+
return parsed.agent_name;
|
|
813
|
+
}
|
|
814
|
+
// Minted delegated tokens omit `agent_name` and carry the agent as the JWT
|
|
815
|
+
// subject, e.g. sub: "agent_gil-ramp-bookkeeper".
|
|
816
|
+
if (typeof parsed.sub === "string" && parsed.sub.startsWith("agent_") && parsed.sub.length > "agent_".length) {
|
|
817
|
+
return parsed.sub.slice("agent_".length);
|
|
818
|
+
}
|
|
819
|
+
return undefined;
|
|
794
820
|
}
|
|
795
821
|
catch {
|
|
796
822
|
return undefined;
|
|
@@ -2177,7 +2203,11 @@ export class RelayFileClient {
|
|
|
2177
2203
|
const payload = await this.readPayload(response);
|
|
2178
2204
|
if (this.shouldRetryStatus(response.status, retries, params.signal)) {
|
|
2179
2205
|
retries += 1;
|
|
2180
|
-
await this.sleep(this.computeRetryDelayMs(retries, response.headers.get("retry-after")
|
|
2206
|
+
await this.sleep(this.computeRetryDelayMs(retries, response.headers.get("retry-after"),
|
|
2207
|
+
// `details.retryAfterSeconds` is a 429-only backpressure signal
|
|
2208
|
+
// (`workspace_busy` / `queue_full`). Only consult the body on a 429
|
|
2209
|
+
// so a 5xx body can never bypass `maxDelayMs` via this path.
|
|
2210
|
+
response.status === 429 ? payload : undefined), params.signal);
|
|
2181
2211
|
continue;
|
|
2182
2212
|
}
|
|
2183
2213
|
this.throwForError(response.status, payload, response.headers);
|
|
@@ -2201,17 +2231,53 @@ export class RelayFileClient {
|
|
|
2201
2231
|
}
|
|
2202
2232
|
return retries < this.retryOptions.maxRetries;
|
|
2203
2233
|
}
|
|
2204
|
-
computeRetryDelayMs(retryAttempt, retryAfterHeader) {
|
|
2234
|
+
computeRetryDelayMs(retryAttempt, retryAfterHeader, payload) {
|
|
2235
|
+
// A server-advertised `Retry-After` HEADER is the standard, explicit
|
|
2236
|
+
// signal and takes precedence: parse it first and leave its handling
|
|
2237
|
+
// unchanged (bounded by our own `maxDelayMs`). Only when the header is
|
|
2238
|
+
// absent or unparseable do we consult the body below, so a body hint never
|
|
2239
|
+
// silently overrides a shorter, explicit header the server already sent.
|
|
2205
2240
|
const retryAfterMs = this.parseRetryAfterMs(retryAfterHeader);
|
|
2206
2241
|
if (retryAfterMs !== null) {
|
|
2207
2242
|
return Math.min(this.retryOptions.maxDelayMs, retryAfterMs);
|
|
2208
2243
|
}
|
|
2244
|
+
// With no usable header, a 429 body can still advertise an explicit
|
|
2245
|
+
// backpressure delay as `details.retryAfterSeconds` (e.g. `workspace_busy`
|
|
2246
|
+
// when the workspace durable object is overloaded, or `queue_full`). Honor
|
|
2247
|
+
// it as an instruction, bounded only by RETRY_AFTER_MAX_MS — NOT truncated
|
|
2248
|
+
// to `maxDelayMs`, which governs our own exponential backoff. Truncating it
|
|
2249
|
+
// (maxDelayMs defaults to 2s vs. a typical 5s advertised delay) retries
|
|
2250
|
+
// into the still-busy resource and exhausts the retry budget.
|
|
2251
|
+
const advertisedMs = this.parseRetryAfterSecondsFromBody(payload);
|
|
2252
|
+
if (advertisedMs !== null) {
|
|
2253
|
+
return Math.max(0, Math.min(RETRY_AFTER_MAX_MS, advertisedMs));
|
|
2254
|
+
}
|
|
2209
2255
|
const backoff = this.retryOptions.baseDelayMs * Math.pow(2, Math.max(0, retryAttempt - 1));
|
|
2210
2256
|
const capped = Math.min(this.retryOptions.maxDelayMs, backoff);
|
|
2211
2257
|
const jitter = this.retryOptions.jitterRatio;
|
|
2212
2258
|
const factor = 1 + (Math.random() * 2 - 1) * jitter;
|
|
2213
2259
|
return Math.max(0, Math.round(capped * factor));
|
|
2214
2260
|
}
|
|
2261
|
+
/**
|
|
2262
|
+
* Extract a server-advertised retry delay from a parsed 429 error body.
|
|
2263
|
+
* Relayfile advertises backpressure delays as `details.retryAfterSeconds`
|
|
2264
|
+
* (in seconds) on `workspace_busy` / `queue_full` responses. Returns
|
|
2265
|
+
* milliseconds, or null when absent/malformed.
|
|
2266
|
+
*/
|
|
2267
|
+
parseRetryAfterSecondsFromBody(payload) {
|
|
2268
|
+
if (!payload || typeof payload !== "object") {
|
|
2269
|
+
return null;
|
|
2270
|
+
}
|
|
2271
|
+
const details = payload.details;
|
|
2272
|
+
if (!details || typeof details !== "object") {
|
|
2273
|
+
return null;
|
|
2274
|
+
}
|
|
2275
|
+
const seconds = details.retryAfterSeconds;
|
|
2276
|
+
if (typeof seconds === "number" && Number.isFinite(seconds) && seconds >= 0) {
|
|
2277
|
+
return seconds * 1000;
|
|
2278
|
+
}
|
|
2279
|
+
return null;
|
|
2280
|
+
}
|
|
2215
2281
|
parseRetryAfterMs(retryAfterHeader) {
|
|
2216
2282
|
if (!retryAfterHeader) {
|
|
2217
2283
|
return null;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@relayfile/sdk",
|
|
3
|
-
"version": "0.10.
|
|
3
|
+
"version": "0.10.52",
|
|
4
4
|
"description": "TypeScript SDK for relayfile — real-time filesystem for humans and agents",
|
|
5
5
|
"main": "dist/index.js",
|
|
6
6
|
"types": "dist/index.d.ts",
|
|
@@ -59,15 +59,15 @@
|
|
|
59
59
|
"prepublishOnly": "npm run build"
|
|
60
60
|
},
|
|
61
61
|
"dependencies": {
|
|
62
|
-
"@relayfile/core": "0.10.
|
|
62
|
+
"@relayfile/core": "0.10.52",
|
|
63
63
|
"ignore": "^7.0.5",
|
|
64
64
|
"tar": "^7.5.10"
|
|
65
65
|
},
|
|
66
66
|
"optionalDependencies": {
|
|
67
|
-
"@relayfile/mount-darwin-arm64": "0.10.
|
|
68
|
-
"@relayfile/mount-darwin-x64": "0.10.
|
|
69
|
-
"@relayfile/mount-linux-arm64": "0.10.
|
|
70
|
-
"@relayfile/mount-linux-x64": "0.10.
|
|
67
|
+
"@relayfile/mount-darwin-arm64": "0.10.52",
|
|
68
|
+
"@relayfile/mount-darwin-x64": "0.10.52",
|
|
69
|
+
"@relayfile/mount-linux-arm64": "0.10.52",
|
|
70
|
+
"@relayfile/mount-linux-x64": "0.10.52"
|
|
71
71
|
},
|
|
72
72
|
"devDependencies": {
|
|
73
73
|
"typescript": "^5.7.3",
|