reploid 0.0.0-stage → 0.1.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.
- package/apps/poolday/config/chat-files.json +27 -0
- package/apps/poolday/config/partition-generation.js +21 -0
- package/apps/poolday/config/partition-policy.json +74 -0
- package/apps/poolday/config/partition-releases.json +3 -0
- package/apps/poolday/config/work-profile.json +122 -0
- package/apps/poolday/host/chat-view.js +47 -0
- package/apps/poolday/host/earning/client.js +199 -0
- package/apps/poolday/host/earning/contribution.js +168 -0
- package/apps/poolday/host/partition-release.js +33 -0
- package/apps/poolday/host/work-partition-artifact-source.js +37 -0
- package/apps/poolday/host/work-partition-capsule.js +39 -0
- package/apps/poolday/host/work-partition-plan.js +167 -0
- package/apps/poolday/host/work-partition-tokenizer-guard.js +21 -0
- package/apps/poolday/infrastructure/model-storage-lock.js +22 -0
- package/apps/poolday/infrastructure/pack-release-storage.js +52 -0
- package/apps/poolday/pool/pack-operation.js +161 -0
- package/apps/poolday/pool/pack-release-policy.js +44 -0
- package/apps/poolday/pool/peer-pack-data-channel.js +217 -0
- package/apps/poolday/providers/work-adapter.js +67 -0
- package/apps/poolday/providers/work-device.js +33 -0
- package/apps/poolday/providers/work-generation-completion.js +55 -0
- package/apps/poolday/providers/work-provider.js +209 -0
- package/apps/poolday/providers/work-resident-provider.js +113 -0
- package/deploy/artifacts/doppler-gpu-0.7.1.tgz +0 -0
- package/docs/fleet-service.md +214 -0
- package/docs/native-participant.md +534 -0
- package/native-delivery.json +1174 -0
- package/package.json +29 -4
- package/packages/reploid/src/artifacts/binary.js +25 -0
- package/packages/reploid/src/artifacts/canonical-json.js +23 -0
- package/packages/reploid/src/artifacts/custody/exchange.js +402 -0
- package/packages/reploid/src/artifacts/custody/piece-acquisition.js +70 -0
- package/packages/reploid/src/artifacts/custody/runtime.js +404 -0
- package/packages/reploid/src/artifacts/custody/supply-budget.js +19 -0
- package/packages/reploid/src/artifacts/custody-certificate.js +123 -0
- package/packages/reploid/src/artifacts/delivery-attestation.js +64 -0
- package/packages/reploid/src/artifacts/generation-terminal.js +36 -0
- package/packages/reploid/src/artifacts/identity.js +91 -0
- package/packages/reploid/src/artifacts/participant-proofs.js +159 -0
- package/packages/reploid/src/artifacts/receipt.js +131 -0
- package/packages/reploid/src/artifacts/signatures.js +94 -0
- package/packages/reploid/src/artifacts/whole-model-receipt.js +42 -0
- package/packages/reploid/src/cancellation.js +44 -0
- package/packages/reploid/src/config/defaults.json +110 -0
- package/packages/reploid/src/config/index.js +209 -0
- package/packages/reploid/src/config/request-overrides.json +9 -0
- package/packages/reploid/src/config/transport-defaults.json +7 -0
- package/packages/reploid/src/hashing.js +30 -0
- package/packages/reploid/src/measurement/capacity-utilization.js +321 -0
- package/packages/reploid/src/measurement/index.js +5 -0
- package/packages/reploid/src/measurement/readiness-records.js +310 -0
- package/packages/reploid/src/mesh/computer-identity.js +50 -0
- package/packages/reploid/src/mesh/computer-identity.policy.json +8 -0
- package/packages/reploid/src/mesh/contribution.js +141 -0
- package/packages/reploid/src/mesh/generation-recovery.js +58 -0
- package/packages/reploid/src/mesh/gpu-signature-shader.js +27 -0
- package/packages/reploid/src/mesh/gpu-signature.js +136 -0
- package/packages/reploid/src/mesh/gpu-signature.policy.json +11 -0
- package/packages/reploid/src/mesh/index.js +66 -0
- package/packages/reploid/src/mesh/partitions/automatic-ordered-partitions.js +540 -0
- package/packages/reploid/src/mesh/partitions/automatic-partitions.js +298 -0
- package/packages/reploid/src/mesh/partitions/partition-attempt-evidence.js +282 -0
- package/packages/reploid/src/mesh/partitions/partition-attempt-queue-policy.json +6 -0
- package/packages/reploid/src/mesh/partitions/partition-attempt-queue.js +112 -0
- package/packages/reploid/src/mesh/partitions/partition-canonical-contract.js +167 -0
- package/packages/reploid/src/mesh/partitions/partition-canonical-execution.js +168 -0
- package/packages/reploid/src/mesh/partitions/partition-chat-canonical.js +49 -0
- package/packages/reploid/src/mesh/partitions/partition-chat.js +125 -0
- package/packages/reploid/src/mesh/partitions/partition-contract.js +192 -0
- package/packages/reploid/src/mesh/partitions/partition-device-admission.js +96 -0
- package/packages/reploid/src/mesh/partitions/partition-discovery.js +96 -0
- package/packages/reploid/src/mesh/partitions/partition-entry-publication.js +183 -0
- package/packages/reploid/src/mesh/partitions/partition-entry.js +434 -0
- package/packages/reploid/src/mesh/partitions/partition-generation-request.js +33 -0
- package/packages/reploid/src/mesh/partitions/partition-grants.js +277 -0
- package/packages/reploid/src/mesh/partitions/partition-link.js +103 -0
- package/packages/reploid/src/mesh/partitions/partition-network.js +109 -0
- package/packages/reploid/src/mesh/partitions/partition-ordered-chat.js +290 -0
- package/packages/reploid/src/mesh/partitions/partition-ordered-peer.js +415 -0
- package/packages/reploid/src/mesh/partitions/partition-ordered-publication.js +100 -0
- package/packages/reploid/src/mesh/partitions/partition-peer.js +314 -0
- package/packages/reploid/src/mesh/partitions/partition-placement.js +143 -0
- package/packages/reploid/src/mesh/partitions/partition-preparation-progress.js +25 -0
- package/packages/reploid/src/mesh/partitions/partition-receiver-policy.json +7 -0
- package/packages/reploid/src/mesh/partitions/partition-reservations.js +75 -0
- package/packages/reploid/src/mesh/partitions/partition-route-envelope-policy.json +16 -0
- package/packages/reploid/src/mesh/partitions/partition-route-envelope.js +74 -0
- package/packages/reploid/src/mesh/partitions/partition-route-execution.js +409 -0
- package/packages/reploid/src/mesh/partitions/partition-route-network.js +179 -0
- package/packages/reploid/src/mesh/partitions/partition-route-ranking.js +268 -0
- package/packages/reploid/src/mesh/partitions/partition-runner.js +319 -0
- package/packages/reploid/src/mesh/partitions/partition-stage-assignment.js +109 -0
- package/packages/reploid/src/mesh/partitions/partition-stage-commit.js +103 -0
- package/packages/reploid/src/mesh/partitions/partition-step-receiver.js +291 -0
- package/packages/reploid/src/mesh/partitions/partition-summary.js +122 -0
- package/packages/reploid/src/mesh/partitions/preparation-owner.js +40 -0
- package/packages/reploid/src/mesh/partitions/resident-error-codes.json +10 -0
- package/packages/reploid/src/mesh/partitions/resident-partition.js +333 -0
- package/packages/reploid/src/mesh/partitions/resident-route.js +46 -0
- package/packages/reploid/src/mesh/partitions/signed-resident-factory.js +59 -0
- package/packages/reploid/src/mesh/peer-generation.js +896 -0
- package/packages/reploid/src/mesh/peer-identity.js +87 -0
- package/packages/reploid/src/mesh/placement-beliefs.js +107 -0
- package/packages/reploid/src/mesh/preparation-progress.js +46 -0
- package/packages/reploid/src/mesh/provider-hardware.js +11 -0
- package/packages/reploid/src/mesh/remote-generation-requests.js +53 -0
- package/packages/reploid/src/mesh/swarm-coordination.js +140 -0
- package/packages/reploid/src/mesh/web-access-network.js +112 -0
- package/packages/reploid/src/mesh/web-access-policy.json +17 -0
- package/packages/reploid/src/retrieval/citations.js +156 -0
- package/packages/reploid/src/retrieval/claim-support.js +113 -0
- package/packages/reploid/src/retrieval/costs.js +101 -0
- package/packages/reploid/src/retrieval/fixed-provider.js +34 -0
- package/packages/reploid/src/retrieval/index.js +20 -0
- package/packages/reploid/src/retrieval/lexical-support.js +27 -0
- package/packages/reploid/src/retrieval/model-reviewer.js +67 -0
- package/packages/reploid/src/retrieval/page-fetch.json +10 -0
- package/packages/reploid/src/retrieval/page-text.js +182 -0
- package/packages/reploid/src/retrieval/passage.js +37 -0
- package/packages/reploid/src/retrieval/policy.json +14 -0
- package/packages/reploid/src/retrieval/retrieval.js +23 -0
- package/packages/reploid/src/retrieval/review-queue.js +76 -0
- package/packages/reploid/src/retrieval/sources.js +263 -0
- package/packages/reploid/src/retrieval/support-review.js +155 -0
- package/packages/reploid/src/retrieval/text.js +21 -0
- package/packages/reploid/src/rules/contribution.rules.json +8 -0
- package/packages/reploid/src/rules/routing.rules.json +24 -0
- package/packages/reploid/src/transport/assignment.js +571 -0
- package/packages/reploid/src/transport/bounded-channel-writer.js +46 -0
- package/packages/reploid/src/transport/index.js +11 -0
- package/packages/reploid/src/transport/introduction-admission.js +24 -0
- package/packages/reploid/src/transport/introduction-bootstrap.js +112 -0
- package/packages/reploid/src/transport/introduction-policy.json +12 -0
- package/packages/reploid/src/transport/introduction-records.js +90 -0
- package/packages/reploid/src/transport/introductions.js +209 -0
- package/packages/reploid/src/transport/partition-data-channel.js +421 -0
- package/packages/reploid/src/transport/partition-errors.json +16 -0
- package/packages/reploid/src/transport/retry-policy.js +36 -0
- package/packages/reploid/src/transport/room.js +502 -0
- package/packages/reploid/src/transport/sdp-fingerprint.js +5 -0
- package/packages/reploid/src/transport/signaling.js +571 -0
- package/packages/reploid/src/transport/swarm.js +1247 -0
- package/packages/reploid/src/transport/tool-offer-channel.js +137 -0
- package/server/browser-roots.js +42 -0
- package/server/browser-web-worker.js +94 -0
- package/server/chat-completions.js +45 -0
- package/server/fleet/browser-bridge.js +145 -0
- package/server/fleet/contract.js +42 -0
- package/server/fleet/credit-evidence.js +103 -0
- package/server/fleet/credit-recovery.js +175 -0
- package/server/fleet/credits.js +178 -0
- package/server/fleet/execution-admission.js +32 -0
- package/server/fleet/native-host.js +817 -0
- package/server/fleet/placement.js +132 -0
- package/server/fleet/requester.js +346 -0
- package/server/fleet/service.js +311 -0
- package/server/fleet/terminal-policy.js +83 -0
- package/server/fleet/text-api.js +94 -0
- package/server/hashing.js +35 -0
- package/server/http-request-owner.js +49 -0
- package/server/messages.js +57 -0
- package/server/native-canonical-epochs.js +90 -0
- package/server/native-change-log.js +40 -0
- package/server/native-earning.js +203 -0
- package/server/native-generation-journal.js +46 -0
- package/server/native-model-config.js +57 -0
- package/server/native-model-files.js +382 -0
- package/server/native-model-origin.js +54 -0
- package/server/native-model-origins.json +12 -0
- package/server/native-node.js +83 -0
- package/server/native-owner/controls.js +162 -0
- package/server/native-owner/monitor.js +251 -0
- package/server/native-owner/policy.js +273 -0
- package/server/native-owner/rate.js +92 -0
- package/server/native-participant.js +866 -0
- package/server/native-readiness-log.js +30 -0
- package/server/native-release-storage.js +64 -0
- package/server/native-shader-cache.js +369 -0
- package/server/native-supply-budget.js +66 -0
- package/server/native-whole-earning.js +67 -0
- package/server/native-whole-model-fill.js +182 -0
- package/server/native-whole-model.js +130 -0
- package/server/public-web-worker.js +66 -0
- package/server/responses.js +92 -0
- package/server/search/errors.js +47 -0
- package/server/search/page-fetch.js +185 -0
- package/server/text-api.js +83 -0
- package/server/text-recovery.js +45 -0
- package/server/text-request-options.js +23 -0
- package/server/text-requester.js +231 -0
- package/server/token-auth.js +9 -0
- package/server/whole-model-requester.js +99 -0
- package/shared/config/chat-models.json +4685 -0
- package/shared/config/doppler-execution-contracts.js +32 -0
- package/shared/config/doppler-execution-contracts.json +43 -0
- package/shared/config/doppler-generation-contract.js +223 -0
- package/shared/config/doppler-local-models.js +79 -0
- package/shared/config/doppler-package.json +7 -0
- package/shared/config/model-manifests/a0dc69e917133a82780823d17fc771c0f6b882d7c86c571deeaef45be2ff108b.json +6434 -0
- package/shared/config/model-manifests/e985e3cdad60d0cb57d84b436161e66ab5b3f6589a168de6f00b4610e5f1fb59.json +16534 -0
- package/shared/config/model-manifests/fa6eb7bbc3ba63571f8cede780b877d9272bde948697af93dd3d33837cb46137.json +7175 -0
- package/shared/config/model-pieces/qwen-0-8b-pieces.json +1506 -0
- package/shared/config/model-pieces/qwen-2b-pieces.json +4800 -0
- package/shared/config/model-pieces/qwen35b-q4_0-pieces.json +28880 -0
- package/shared/config/reploid-library.json +49 -0
- package/shared/config/swarm-bootstrap.json +21 -0
- package/shared/infrastructure/doppler-runtime-service.js +261 -0
- package/shared/pool/executable-pack.js +87 -0
- package/shared/pool/pack-operation-adapters.js +124 -0
- package/shared/pool/pack-operation-policy.js +75 -0
- package/shared/pool/pool-config.json +2280 -0
- package/shared/vendor/reploid/adapters/doppler-session.js +372 -0
- package/shared/vendor/reploid/artifacts/binary.js +25 -0
- package/shared/vendor/reploid/artifacts/canonical-json.js +23 -0
- package/shared/vendor/reploid/artifacts/delivery-attestation.js +64 -0
- package/shared/vendor/reploid/artifacts/identity.js +91 -0
- package/shared/vendor/reploid/artifacts/manifest-identity.js +17 -0
- package/shared/vendor/reploid/artifacts/participant-proofs.js +159 -0
- package/shared/vendor/reploid/artifacts/signatures.js +94 -0
- package/shared/vendor/reploid/config/defaults.json +110 -0
- package/shared/vendor/reploid/config/index.js +209 -0
- package/shared/vendor/reploid/config/request-overrides.json +9 -0
- package/shared/vendor/reploid/hashing.js +30 -0
- package/shared/vendor/reploid/mesh/generation-context-cache.js +97 -0
- package/shared/vendor/reploid/mesh/partitions/partition-attempt-evidence.js +282 -0
- package/shared/vendor/reploid/mesh/partitions/partition-contract.js +192 -0
- package/shared/vendor/reploid/mesh/partitions/partition-receiver-policy.json +7 -0
- package/shared/vendor/reploid/mesh/partitions/partition-route-ranking.js +268 -0
- package/shared/vendor/reploid/mesh/preparation-progress.js +46 -0
- package/shared/vendor/reploid/mesh/provider-hardware.js +11 -0
- package/shared/vendor/reploid/rules/routing.rules.json +24 -0
- package/shared/vendor/reploid/transport/partition-errors.json +16 -0
- package/README.md +0 -3
|
@@ -0,0 +1,421 @@
|
|
|
1
|
+
import { createBoundedChannelWriter } from './bounded-channel-writer.js';
|
|
2
|
+
import failureMessages from './partition-errors.json' with { type: 'json' };
|
|
3
|
+
|
|
4
|
+
const SCHEMA = 'reploid.partition-channel/v2';
|
|
5
|
+
// Fragment header: kind (request or response payload), request id, offset, length.
|
|
6
|
+
const HEADER_BYTES = 13;
|
|
7
|
+
const REQUEST_PAYLOAD = 0, RESPONSE_PAYLOAD = 1;
|
|
8
|
+
const encoder = new TextEncoder();
|
|
9
|
+
const positive = value => Number.isSafeInteger(value) && value > 0;
|
|
10
|
+
const count = value => Number.isSafeInteger(value) && value >= 0;
|
|
11
|
+
const assert = (condition, message) => { if (!condition) throw new Error(`Partition channel: ${message}`); };
|
|
12
|
+
const object = value => value !== null && typeof value === 'object' && !Array.isArray(value);
|
|
13
|
+
const binaryResults = new WeakSet();
|
|
14
|
+
|
|
15
|
+
/** A serve result that returns bytes on the direct reply path, bounded by the
|
|
16
|
+
* request's declared `maxResponseBytes`. */
|
|
17
|
+
export function partitionBinaryResult(result, bytes) {
|
|
18
|
+
assert(object(result) && bytes instanceof Uint8Array, 'binary result requires a JSON object and bytes');
|
|
19
|
+
const value = Object.freeze({ result, bytes });
|
|
20
|
+
binaryResults.add(value);
|
|
21
|
+
return value;
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
/** Binary requests and bounded JSON (optionally binary) replies on an owned, dedicated RTC channel.
|
|
25
|
+
* Pipelined: a request's header and payload leave together, under credit that the receiver's
|
|
26
|
+
* declared window grants and its terminal reply (result, error or released) returns.
|
|
27
|
+
* Host ports bind grants to authenticated peers. Receipt/abort is not GPU settlement.
|
|
28
|
+
*/
|
|
29
|
+
export function createPartitionDataChannel({ channel, localParticipantId, remoteParticipantId, authorize, serve, limits }) {
|
|
30
|
+
assert(channel?.ordered === true && channel.maxRetransmits == null && channel.maxPacketLifeTime == null,
|
|
31
|
+
'reliable ordered channel required');
|
|
32
|
+
assert(typeof authorize === 'function' && typeof serve === 'function', 'authorization and serving ports required');
|
|
33
|
+
assert([localParticipantId, remoteParticipantId].every(id => typeof id === 'string' && id.length > 0 && id.length <= 256)
|
|
34
|
+
&& localParticipantId !== remoteParticipantId, 'distinct authenticated peer identities required');
|
|
35
|
+
for (const key of ['maxFrameBytes', 'maxControlBytes', 'maxPayloadBytes', 'maxPendingBytes',
|
|
36
|
+
'maxPendingRequests', 'maxRequestsPerChannel', 'maxBufferedBytes', 'maxTransferBytes', 'timeoutMs']) {
|
|
37
|
+
assert(positive(limits?.[key]), `explicit ${key} required`);
|
|
38
|
+
}
|
|
39
|
+
const policy = Object.freeze(structuredClone(limits));
|
|
40
|
+
assert(policy.maxFrameBytes > HEADER_BYTES && policy.maxFrameBytes <= policy.maxBufferedBytes
|
|
41
|
+
&& policy.maxControlBytes <= policy.maxBufferedBytes && policy.maxPayloadBytes <= 0xffffffff
|
|
42
|
+
&& policy.maxPayloadBytes <= policy.maxPendingBytes && policy.maxRequestsPerChannel <= 0xffffffff
|
|
43
|
+
&& policy.timeoutMs <= 2147483647, 'invalid limits');
|
|
44
|
+
const fragmentBytes = policy.maxFrameBytes - HEADER_BYTES;
|
|
45
|
+
// Requests this endpoint sent, and the peer's requests it holds. Each direction is bounded by
|
|
46
|
+
// the declared window (maxPendingRequests, maxPendingBytes): both endpoints of a channel declare
|
|
47
|
+
// the same channel policy, and a peer that exceeds this endpoint's window closes the channel.
|
|
48
|
+
const outgoing = new Map();
|
|
49
|
+
const incoming = new Map();
|
|
50
|
+
const receivedIds = new Set();
|
|
51
|
+
// Credit: the peer's window less what its terminal replies have not yet returned.
|
|
52
|
+
const held = new Map();
|
|
53
|
+
const waiting = [];
|
|
54
|
+
// Response fragments of requests that already ended here are skipped, never buffered.
|
|
55
|
+
const skipping = new Map();
|
|
56
|
+
const lifecycle = new AbortController();
|
|
57
|
+
const counters = { sentFrameBytes: 0, receivedFrameBytes: 0, sentFrames: 0, receivedFrames: 0,
|
|
58
|
+
completedRequests: 0, cancelledRequests: 0, discardedFrames: 0 };
|
|
59
|
+
let nextId = 0;
|
|
60
|
+
let outgoingBytes = 0;
|
|
61
|
+
let incomingBytes = 0;
|
|
62
|
+
let closed = false;
|
|
63
|
+
let lastFailure = null;
|
|
64
|
+
const failureCode = error => Object.hasOwn(failureMessages, error?.code) ? error.code : 'EXECUTION_FAILED';
|
|
65
|
+
const coded = code => Object.assign(new Error(failureMessages[code]), { code });
|
|
66
|
+
const recordFailure = (entry, phase, error) => {
|
|
67
|
+
lastFailure = { requestId: entry.id, phase, code: failureCode(error),
|
|
68
|
+
message: String(error?.message || error).slice(0, policy.maxControlBytes),
|
|
69
|
+
failedAt: Date.now(), settledAt: null };
|
|
70
|
+
};
|
|
71
|
+
channel.binaryType = 'arraybuffer';
|
|
72
|
+
channel.bufferedAmountLowThreshold = 0;
|
|
73
|
+
const send = createBoundedChannelWriter({ channel, limits: policy, signal: lifecycle.signal,
|
|
74
|
+
account: size => { counters.sentFrameBytes += size; counters.sentFrames++; } });
|
|
75
|
+
|
|
76
|
+
function encodeControl(frame) {
|
|
77
|
+
const text = JSON.stringify({ schema: SCHEMA, ...frame });
|
|
78
|
+
assert(encoder.encode(text).byteLength <= policy.maxControlBytes, 'control frame too large');
|
|
79
|
+
return text;
|
|
80
|
+
}
|
|
81
|
+
const control = (frame, signal) => send(encodeControl(frame), signal);
|
|
82
|
+
async function sendPayload(kind, id, bytes, signal) {
|
|
83
|
+
for (let offset = 0; offset < bytes.length; offset += fragmentBytes) {
|
|
84
|
+
const part = bytes.subarray(offset, offset + fragmentBytes);
|
|
85
|
+
const frame = new Uint8Array(HEADER_BYTES + part.length);
|
|
86
|
+
const view = new DataView(frame.buffer);
|
|
87
|
+
view.setUint8(0, kind);
|
|
88
|
+
view.setUint32(1, id);
|
|
89
|
+
view.setUint32(5, offset);
|
|
90
|
+
view.setUint32(9, part.length);
|
|
91
|
+
frame.set(part, HEADER_BYTES);
|
|
92
|
+
await send(frame.buffer, signal);
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
async function permit(action, entry, responseByteLength) {
|
|
96
|
+
entry.controller.signal.throwIfAborted();
|
|
97
|
+
const allowed = await authorize({ action, localParticipantId, remoteParticipantId,
|
|
98
|
+
metadata: structuredClone(entry.metadata), byteLength: entry.size,
|
|
99
|
+
...(responseByteLength === undefined ? {} : { responseByteLength }) },
|
|
100
|
+
{ signal: entry.controller.signal });
|
|
101
|
+
entry.controller.signal.throwIfAborted();
|
|
102
|
+
if (allowed !== true) throw Object.assign(new Error('Partition channel: authorization declined'),
|
|
103
|
+
{ code: 'AUTHORIZATION_DENIED' });
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
// Credit is taken in request order; a request never overtakes an earlier waiting one.
|
|
107
|
+
const heldBytes = () => { let total = 0; for (const size of held.values()) total += size; return total; };
|
|
108
|
+
const fits = entry => held.size < policy.maxPendingRequests && heldBytes() + entry.size <= policy.maxPendingBytes;
|
|
109
|
+
function pump() {
|
|
110
|
+
while (waiting.length && fits(waiting[0].entry)) {
|
|
111
|
+
const next = waiting.shift();
|
|
112
|
+
next.entry.controller.signal.removeEventListener('abort', next.cancel);
|
|
113
|
+
held.set(next.entry.id, next.entry.size);
|
|
114
|
+
next.resolve();
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
function acquireCredit(entry) {
|
|
118
|
+
entry.controller.signal.throwIfAborted();
|
|
119
|
+
if (!waiting.length && fits(entry)) { held.set(entry.id, entry.size); return Promise.resolve(); }
|
|
120
|
+
return new Promise((resolve, reject) => {
|
|
121
|
+
const item = { entry, resolve, cancel: () => {
|
|
122
|
+
const index = waiting.indexOf(item);
|
|
123
|
+
if (index >= 0) waiting.splice(index, 1);
|
|
124
|
+
reject(entry.controller.signal.reason);
|
|
125
|
+
pump();
|
|
126
|
+
} };
|
|
127
|
+
entry.controller.signal.addEventListener('abort', item.cancel, { once: true });
|
|
128
|
+
waiting.push(item);
|
|
129
|
+
});
|
|
130
|
+
}
|
|
131
|
+
function returnCredit(id) {
|
|
132
|
+
assert(held.delete(id), 'unexpected terminal reply');
|
|
133
|
+
pump();
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
function releaseOutgoing(entry) {
|
|
137
|
+
clearTimeout(entry.timer);
|
|
138
|
+
entry.signal?.removeEventListener('abort', entry.abort);
|
|
139
|
+
if (outgoing.delete(entry.id)) outgoingBytes -= entry.reserved;
|
|
140
|
+
// A reply still arriving for an ended request is skipped, never buffered.
|
|
141
|
+
const remaining = entry.response && !entry.complete ? entry.response.length - entry.responseOffset : 0;
|
|
142
|
+
if (remaining > 0) skipping.set(entry.id, remaining);
|
|
143
|
+
entry.bytes = null; entry.response = null;
|
|
144
|
+
}
|
|
145
|
+
function finish(entry, error, value) {
|
|
146
|
+
if (entry.finished) return;
|
|
147
|
+
entry.finished = true;
|
|
148
|
+
clearTimeout(entry.timer);
|
|
149
|
+
entry.signal?.removeEventListener('abort', entry.abort);
|
|
150
|
+
if (!error && entry.onTiming) {
|
|
151
|
+
const completedAt = performance.now();
|
|
152
|
+
// Response waiting includes remote authorization and computation. Credit
|
|
153
|
+
// waiting replaces the former ready round trip and is zero with free credit.
|
|
154
|
+
try { entry.onTiming({ authorizationMs: entry.authorizedAt - entry.startedAt,
|
|
155
|
+
readyWaitMs: entry.uploadStartedAt - entry.authorizedAt,
|
|
156
|
+
payloadUploadMs: entry.uploadFinishedAt - entry.uploadStartedAt,
|
|
157
|
+
responseWaitMs: Math.max(0, entry.resultAt - entry.uploadFinishedAt),
|
|
158
|
+
acceptanceMs: completedAt - Math.max(entry.resultAt, entry.uploadFinishedAt),
|
|
159
|
+
totalMs: completedAt - entry.startedAt }); } catch {}
|
|
160
|
+
}
|
|
161
|
+
error ? entry.reject(error) : entry.resolve(value);
|
|
162
|
+
entry.controller.abort(error ?? new Error('Partition request complete'));
|
|
163
|
+
if (!entry.busy) releaseOutgoing(entry);
|
|
164
|
+
}
|
|
165
|
+
// The receiver releases its window before any terminal reply, so returned credit is exact.
|
|
166
|
+
function releaseIncoming(entry) {
|
|
167
|
+
clearTimeout(entry.timer);
|
|
168
|
+
if (!incoming.delete(entry.id)) return;
|
|
169
|
+
incomingBytes -= entry.size;
|
|
170
|
+
entry.bytes = null;
|
|
171
|
+
if (lastFailure?.requestId === entry.id) lastFailure.settledAt = Date.now();
|
|
172
|
+
if (closed) return;
|
|
173
|
+
const terminal = entry.terminal ?? (entry.timedOut ? { type: 'error', code: 'REQUEST_TIMEOUT' }
|
|
174
|
+
: entry.failure ? { type: 'error', code: entry.failure } : { type: 'released' });
|
|
175
|
+
const response = entry.responsePayload;
|
|
176
|
+
entry.responsePayload = null;
|
|
177
|
+
(async () => {
|
|
178
|
+
await control({ ...terminal, id: entry.id });
|
|
179
|
+
if (response?.length) await sendPayload(RESPONSE_PAYLOAD, entry.id, response);
|
|
180
|
+
})().catch(failDelivery);
|
|
181
|
+
}
|
|
182
|
+
function cancelIncoming(entry) {
|
|
183
|
+
clearTimeout(entry.timer);
|
|
184
|
+
entry.controller.abort(new Error('Partition request cancelled or timed out'));
|
|
185
|
+
// A host callback may ignore abort. Keep its window charged until it settles.
|
|
186
|
+
if (!entry.busy) releaseIncoming(entry);
|
|
187
|
+
}
|
|
188
|
+
function close(reason = 'channel closed') {
|
|
189
|
+
if (closed) return;
|
|
190
|
+
closed = true;
|
|
191
|
+
const error = Object.assign(new Error(failureMessages.CONNECTION_LOST),
|
|
192
|
+
{ code: 'CONNECTION_LOST', cause: new Error(`Partition channel: ${reason}`) });
|
|
193
|
+
lifecycle.abort(error);
|
|
194
|
+
for (const entry of outgoing.values()) finish(entry, error);
|
|
195
|
+
for (const entry of incoming.values()) cancelIncoming(entry);
|
|
196
|
+
channel.removeEventListener('message', onMessage);
|
|
197
|
+
channel.removeEventListener('close', onClose);
|
|
198
|
+
channel.removeEventListener('error', onClose);
|
|
199
|
+
channel.close();
|
|
200
|
+
}
|
|
201
|
+
const failDelivery = () => close('control delivery failed');
|
|
202
|
+
|
|
203
|
+
function accept(frame) {
|
|
204
|
+
assert(!receivedIds.has(frame.id), 'duplicate request');
|
|
205
|
+
assert(frame.id <= policy.maxRequestsPerChannel && receivedIds.size < policy.maxRequestsPerChannel, 'request identity budget exhausted');
|
|
206
|
+
receivedIds.add(frame.id);
|
|
207
|
+
assert(object(frame.metadata) && count(frame.size) && frame.size <= policy.maxPayloadBytes
|
|
208
|
+
&& count(frame.responseBytes) && frame.responseBytes <= policy.maxPayloadBytes, 'request geometry');
|
|
209
|
+
// Credit the peer took: its request and payload must fit the window this endpoint declares.
|
|
210
|
+
assert(incoming.size < policy.maxPendingRequests && incomingBytes + frame.size <= policy.maxPendingBytes, 'receive window exceeded');
|
|
211
|
+
incomingBytes += frame.size;
|
|
212
|
+
const entry = { id: frame.id, metadata: frame.metadata, size: frame.size, responseBytes: frame.responseBytes,
|
|
213
|
+
offset: 0, controller: new AbortController(), busy: true, permitted: false, bytes: new Uint8Array(frame.size) };
|
|
214
|
+
incoming.set(frame.id, entry);
|
|
215
|
+
entry.timer = setTimeout(() => { entry.timedOut = true; cancelIncoming(entry); }, policy.timeoutMs);
|
|
216
|
+
admit(entry);
|
|
217
|
+
}
|
|
218
|
+
// Authorization runs while payload fragments arrive; refused bytes are discarded unexecuted.
|
|
219
|
+
async function admit(entry) {
|
|
220
|
+
try {
|
|
221
|
+
await permit('receive', entry);
|
|
222
|
+
entry.permitted = true;
|
|
223
|
+
} catch (error) {
|
|
224
|
+
recordFailure(entry, 'admission', error);
|
|
225
|
+
if (!entry.controller.signal.aborted) entry.failure = failureCode(error);
|
|
226
|
+
entry.controller.abort(error);
|
|
227
|
+
} finally {
|
|
228
|
+
entry.busy = false;
|
|
229
|
+
if (entry.controller.signal.aborted) releaseIncoming(entry);
|
|
230
|
+
else if (entry.offset === entry.size) execute(entry);
|
|
231
|
+
}
|
|
232
|
+
}
|
|
233
|
+
|
|
234
|
+
async function execute(entry) {
|
|
235
|
+
if (entry.busy || entry.executing) return;
|
|
236
|
+
entry.busy = true;
|
|
237
|
+
entry.executing = true;
|
|
238
|
+
try {
|
|
239
|
+
// Permission may change during a fragmented upload.
|
|
240
|
+
await permit('receive', entry);
|
|
241
|
+
const returned = await serve(structuredClone(entry.metadata), entry.bytes, { signal: entry.controller.signal });
|
|
242
|
+
const binary = binaryResults.has(returned);
|
|
243
|
+
const result = binary ? returned.result : returned;
|
|
244
|
+
const payload = binary ? returned.bytes : null;
|
|
245
|
+
await permit('respond', entry, payload?.length ?? 0);
|
|
246
|
+
assert(object(result), 'JSON result object required');
|
|
247
|
+
assert(!payload || payload.length <= entry.responseBytes, 'response exceeds the declared allocation');
|
|
248
|
+
// Bound the reply before releasing the window that carries it.
|
|
249
|
+
encodeControl({ type: 'result', id: entry.id, result, size: payload?.length ?? 0 });
|
|
250
|
+
entry.terminal = { type: 'result', result, size: payload?.length ?? 0 };
|
|
251
|
+
entry.responsePayload = payload?.slice() ?? null;
|
|
252
|
+
} catch (error) {
|
|
253
|
+
recordFailure(entry, 'execution', error);
|
|
254
|
+
if (!entry.controller.signal.aborted) entry.failure = failureCode(error);
|
|
255
|
+
} finally {
|
|
256
|
+
entry.busy = false;
|
|
257
|
+
releaseIncoming(entry);
|
|
258
|
+
}
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
async function start(entry, text) {
|
|
262
|
+
try {
|
|
263
|
+
await permit('send', entry);
|
|
264
|
+
entry.authorizedAt = performance.now();
|
|
265
|
+
await acquireCredit(entry);
|
|
266
|
+
entry.uploadStartedAt = performance.now();
|
|
267
|
+
try { await send(text, entry.controller.signal); }
|
|
268
|
+
catch (error) { held.delete(entry.id); pump(); throw error; }
|
|
269
|
+
entry.sent = true;
|
|
270
|
+
// The payload follows its header without waiting for a reply.
|
|
271
|
+
await sendPayload(REQUEST_PAYLOAD, entry.id, entry.bytes, entry.controller.signal);
|
|
272
|
+
entry.bytes = null;
|
|
273
|
+
} catch (error) { entry.abort(error); }
|
|
274
|
+
finally {
|
|
275
|
+
entry.uploadFinishedAt = performance.now();
|
|
276
|
+
entry.busy = false;
|
|
277
|
+
if (entry.finished) { if (entry.sent) cancelRemote(entry); releaseOutgoing(entry); }
|
|
278
|
+
else if (entry.complete) disclose(entry);
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
async function disclose(entry) {
|
|
282
|
+
entry.busy = true;
|
|
283
|
+
try {
|
|
284
|
+
await permit('accept', entry, entry.response.length);
|
|
285
|
+
counters.completedRequests++;
|
|
286
|
+
finish(entry, null, entry.binary ? { result: entry.result, bytes: entry.response } : entry.result);
|
|
287
|
+
} catch (error) { finish(entry, error); }
|
|
288
|
+
finally { entry.busy = false; releaseOutgoing(entry); }
|
|
289
|
+
}
|
|
290
|
+
function responseComplete(entry) {
|
|
291
|
+
entry.complete = true;
|
|
292
|
+
if (!entry.busy) disclose(entry);
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
function receiveControl(text) {
|
|
296
|
+
const frame = JSON.parse(text);
|
|
297
|
+
assert(frame?.schema === SCHEMA && positive(frame.id) && frame.id <= 0xffffffff, 'invalid control');
|
|
298
|
+
if (frame.type === 'request') { accept(frame); return; }
|
|
299
|
+
if (frame.type === 'cancel') {
|
|
300
|
+
assert(receivedIds.has(frame.id), 'cancellation before request');
|
|
301
|
+
const entry = incoming.get(frame.id);
|
|
302
|
+
if (entry) cancelIncoming(entry);
|
|
303
|
+
return;
|
|
304
|
+
}
|
|
305
|
+
assert(['result', 'error', 'released'].includes(frame.type) && frame.id <= nextId, 'unexpected response');
|
|
306
|
+
returnCredit(frame.id);
|
|
307
|
+
const size = frame.type === 'result' ? frame.size : 0;
|
|
308
|
+
assert(count(size) && size <= policy.maxPayloadBytes, 'response geometry');
|
|
309
|
+
const entry = outgoing.get(frame.id);
|
|
310
|
+
if (!entry || entry.finished) {
|
|
311
|
+
counters.discardedFrames++;
|
|
312
|
+
if (size) skipping.set(frame.id, size);
|
|
313
|
+
return;
|
|
314
|
+
}
|
|
315
|
+
if (frame.type === 'released') { finish(entry, coded('REQUEST_TIMEOUT')); return; }
|
|
316
|
+
if (frame.type === 'error') {
|
|
317
|
+
const code = failureCode(frame);
|
|
318
|
+
finish(entry, coded(code)); return;
|
|
319
|
+
}
|
|
320
|
+
assert(!entry.result && object(frame.result) && size <= entry.responseBytes, 'unexpected result');
|
|
321
|
+
entry.resultAt = performance.now();
|
|
322
|
+
entry.result = frame.result;
|
|
323
|
+
entry.response = new Uint8Array(size);
|
|
324
|
+
entry.responseOffset = 0;
|
|
325
|
+
if (size === 0) responseComplete(entry);
|
|
326
|
+
}
|
|
327
|
+
function receiveBinary(buffer) {
|
|
328
|
+
assert(buffer.byteLength > HEADER_BYTES, 'empty activation fragment');
|
|
329
|
+
const view = new DataView(buffer);
|
|
330
|
+
const kind = view.getUint8(0), id = view.getUint32(1), offset = view.getUint32(5), size = view.getUint32(9);
|
|
331
|
+
assert(size === buffer.byteLength - HEADER_BYTES, 'activation fragment geometry');
|
|
332
|
+
if (kind === RESPONSE_PAYLOAD) {
|
|
333
|
+
const skipped = skipping.get(id);
|
|
334
|
+
if (skipped !== undefined) {
|
|
335
|
+
counters.discardedFrames++;
|
|
336
|
+
if (skipped - size > 0) skipping.set(id, skipped - size); else skipping.delete(id);
|
|
337
|
+
return;
|
|
338
|
+
}
|
|
339
|
+
const entry = outgoing.get(id);
|
|
340
|
+
assert(entry?.response && !entry.complete && offset === entry.responseOffset
|
|
341
|
+
&& size === Math.min(fragmentBytes, entry.response.length - offset), 'response fragment geometry');
|
|
342
|
+
entry.response.set(new Uint8Array(buffer, HEADER_BYTES), offset);
|
|
343
|
+
entry.responseOffset += size;
|
|
344
|
+
if (entry.responseOffset === entry.response.length) responseComplete(entry);
|
|
345
|
+
return;
|
|
346
|
+
}
|
|
347
|
+
assert(kind === REQUEST_PAYLOAD && receivedIds.has(id), 'fragment before request');
|
|
348
|
+
const entry = incoming.get(id);
|
|
349
|
+
if (!entry || entry.controller.signal.aborted) { counters.discardedFrames++; return; }
|
|
350
|
+
assert(!entry.executing && offset === entry.offset
|
|
351
|
+
&& size === Math.min(fragmentBytes, entry.size - offset), 'activation fragment geometry');
|
|
352
|
+
entry.bytes.set(new Uint8Array(buffer, HEADER_BYTES), offset);
|
|
353
|
+
entry.offset += size;
|
|
354
|
+
if (entry.offset === entry.size && entry.permitted && !entry.busy) execute(entry);
|
|
355
|
+
}
|
|
356
|
+
function onMessage({ data }) {
|
|
357
|
+
try {
|
|
358
|
+
const text = typeof data === 'string';
|
|
359
|
+
assert(text || data instanceof ArrayBuffer, 'unsupported frame');
|
|
360
|
+
const size = text ? encoder.encode(data).byteLength : data.byteLength;
|
|
361
|
+
counters.receivedFrameBytes += size;
|
|
362
|
+
counters.receivedFrames++;
|
|
363
|
+
assert(size <= (text ? policy.maxControlBytes : policy.maxFrameBytes), 'frame limit');
|
|
364
|
+
assert(counters.receivedFrameBytes <= policy.maxTransferBytes, 'incoming byte budget exhausted');
|
|
365
|
+
text ? receiveControl(data) : receiveBinary(data);
|
|
366
|
+
} catch (error) { close(error.message); }
|
|
367
|
+
}
|
|
368
|
+
const onClose = () => close('peer disconnected');
|
|
369
|
+
channel.addEventListener('message', onMessage);
|
|
370
|
+
channel.addEventListener('close', onClose);
|
|
371
|
+
channel.addEventListener('error', onClose);
|
|
372
|
+
|
|
373
|
+
return Object.freeze({
|
|
374
|
+
request(metadata, bytes, { signal, onTiming, maxResponseBytes } = {}) {
|
|
375
|
+
const startedAt = performance.now();
|
|
376
|
+
if (closed || channel.readyState !== 'open') {
|
|
377
|
+
throw Object.assign(new Error(failureMessages.CONNECTION_LOST), { code: 'CONNECTION_LOST' });
|
|
378
|
+
}
|
|
379
|
+
signal?.throwIfAborted();
|
|
380
|
+
const binary = maxResponseBytes !== undefined;
|
|
381
|
+
assert(object(metadata) && bytes instanceof Uint8Array && bytes.length <= policy.maxPayloadBytes, 'request geometry');
|
|
382
|
+
assert(!binary || count(maxResponseBytes) && maxResponseBytes <= policy.maxPayloadBytes, 'response geometry');
|
|
383
|
+
assert(nextId < policy.maxRequestsPerChannel, 'request identity budget exhausted');
|
|
384
|
+
// Bound metadata before copying or invoking any asynchronous host port.
|
|
385
|
+
const responseBytes = binary ? maxResponseBytes : 0;
|
|
386
|
+
const text = encodeControl({ type: 'request', id: nextId + 1, metadata, size: bytes.length, responseBytes });
|
|
387
|
+
// Local memory: the payload copy until written, and the reply allocation it may receive.
|
|
388
|
+
const reserved = bytes.length + responseBytes;
|
|
389
|
+
assert(outgoing.size < policy.maxPendingRequests, 'pending request limit');
|
|
390
|
+
assert(outgoingBytes + reserved <= policy.maxPendingBytes, 'pending byte limit');
|
|
391
|
+
outgoingBytes += reserved;
|
|
392
|
+
const entry = { metadata: JSON.parse(text).metadata, bytes: bytes.slice(), size: bytes.length, responseBytes, reserved, binary,
|
|
393
|
+
controller: new AbortController(), busy: true, finished: false, signal, onTiming, startedAt, id: ++nextId };
|
|
394
|
+
outgoing.set(entry.id, entry);
|
|
395
|
+
const result = new Promise((resolve, reject) => { entry.resolve = resolve; entry.reject = reject; });
|
|
396
|
+
entry.abort = error => {
|
|
397
|
+
if (entry.finished) return;
|
|
398
|
+
counters.cancelledRequests++;
|
|
399
|
+
finish(entry, error instanceof Error ? error : new Error('Partition request cancelled or timed out'));
|
|
400
|
+
cancelRemote(entry);
|
|
401
|
+
};
|
|
402
|
+
signal?.addEventListener('abort', entry.abort, { once: true });
|
|
403
|
+
entry.timer = setTimeout(() => entry.abort(coded('REQUEST_TIMEOUT')), policy.timeoutMs);
|
|
404
|
+
start(entry, text);
|
|
405
|
+
return result;
|
|
406
|
+
},
|
|
407
|
+
close,
|
|
408
|
+
getReceipt: () => ({ schema: SCHEMA, ...counters, pendingRequests: outgoing.size,
|
|
409
|
+
inboundRequests: incoming.size, reservedBytes: outgoingBytes + incomingBytes,
|
|
410
|
+
creditHeld: { requests: held.size, bytes: heldBytes() }, creditWaiting: waiting.length,
|
|
411
|
+
closed, wireBytes: null, relayBytes: null, lastFailure: structuredClone(lastFailure) }),
|
|
412
|
+
});
|
|
413
|
+
|
|
414
|
+
function cancelRemote(entry) {
|
|
415
|
+
// Only a request whose header left needs the peer to release its window.
|
|
416
|
+
if (entry.sent && !entry.cancelSent && !closed && held.has(entry.id)) {
|
|
417
|
+
entry.cancelSent = true;
|
|
418
|
+
control({ type: 'cancel', id: entry.id }).catch(failDelivery);
|
|
419
|
+
}
|
|
420
|
+
}
|
|
421
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{
|
|
2
|
+
"RESULT_EXPIRED": "The retained canonical result expired. Start a new authorized attempt.",
|
|
3
|
+
"RESOURCE_EXHAUSTED": "A contributor exhausted its resource allowance. Retry with prepared capacity or a shorter conversation.",
|
|
4
|
+
"AUTHORIZATION_DENIED": "A contributor declined authorization for this request.",
|
|
5
|
+
"EXECUTION_FAILED": "Partition peer declined or failed request",
|
|
6
|
+
"OUTPUT_LIMIT_EXCEEDED": "A contributor exceeded the answer length allowance. The attempt failed; its partial answer is incomplete.",
|
|
7
|
+
"GENERATION_OUTPUT_INVALID": "A contributor did not produce a valid final answer. The attempt failed; its partial answer is incomplete.",
|
|
8
|
+
"CAPACITY_UNAVAILABLE": "A contributor has no available prepared capacity for this attempt. Select available capacity and retry.",
|
|
9
|
+
"CAPACITY_QUEUE_FULL": "Prepared capacity for this route is busy and its queue is full. The attempt was not run; retry later.",
|
|
10
|
+
"CAPACITY_QUEUE_TIMEOUT": "No prepared capacity for this route became free before this attempt's queue limit or permission window ended. The attempt was not run; retry starts a new attempt.",
|
|
11
|
+
"CONNECTION_LOST": "A contributor disconnected. The attempt was interrupted; remote cleanup is not confirmed.",
|
|
12
|
+
"REQUEST_TIMEOUT": "The contributor request timed out. Remote completion and cleanup are not confirmed.",
|
|
13
|
+
"CLEANUP_FAILED": "Partition attempt settlement failed. Contributor cleanup is not confirmed; its capacity remains unavailable.",
|
|
14
|
+
"ANSWER_DEADLINE_EXCEEDED": "Response timed out. Retry starts a new attempt.",
|
|
15
|
+
"WORKLOAD_OUTSIDE_ENVELOPE": "This route does not support a request of this prompt length, answer length or number of concurrent requests. Nothing was sent to its other contributors."
|
|
16
|
+
}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* @fileoverview Shared bounded retry policy for Poolday browser transports.
|
|
3
|
+
*/
|
|
4
|
+
|
|
5
|
+
const finiteMilliseconds = (value) => {
|
|
6
|
+
const number = Number(value);
|
|
7
|
+
return Number.isFinite(number) && number >= 0 ? number : 0;
|
|
8
|
+
};
|
|
9
|
+
|
|
10
|
+
export function retryAfterMsFromError(error, { maxDelayMs } = {}) {
|
|
11
|
+
const bound = Math.max(1, finiteMilliseconds(maxDelayMs));
|
|
12
|
+
const explicit = finiteMilliseconds(error?.retryAfterMs);
|
|
13
|
+
const seconds = finiteMilliseconds(error?.retryAfter) * 1000;
|
|
14
|
+
const requested = explicit || seconds;
|
|
15
|
+
return Math.min(bound, requested);
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
export function boundedRetryDelay({
|
|
19
|
+
consecutiveFailures = 0,
|
|
20
|
+
baseDelayMs = 1000,
|
|
21
|
+
maxDelayMs = 30000,
|
|
22
|
+
retryAfterMs = 0
|
|
23
|
+
} = {}) {
|
|
24
|
+
const maximum = Math.max(1, finiteMilliseconds(maxDelayMs));
|
|
25
|
+
const base = Math.min(maximum, Math.max(1, finiteMilliseconds(baseDelayMs)));
|
|
26
|
+
const failures = Math.max(0, Math.floor(finiteMilliseconds(consecutiveFailures)));
|
|
27
|
+
const exponential = failures === 0
|
|
28
|
+
? 0
|
|
29
|
+
: Math.min(maximum, base * (2 ** Math.max(0, failures - 1)));
|
|
30
|
+
return Math.max(exponential, Math.min(maximum, finiteMilliseconds(retryAfterMs)));
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export default {
|
|
34
|
+
boundedRetryDelay,
|
|
35
|
+
retryAfterMsFromError
|
|
36
|
+
};
|