reploid 0.0.0-stage → 0.1.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.
- 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 +542 -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 +85 -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,132 @@
|
|
|
1
|
+
import { assert, digest, integer, text } from './contract.js';
|
|
2
|
+
|
|
3
|
+
const key = value => `${value.id}:${value.identity}`;
|
|
4
|
+
const fresh = (at, now, policy) => Number.isFinite(at) && at <= now && now - at <= policy.maxObservationAgeMs;
|
|
5
|
+
const positive = value => Number.isFinite(value) && value > 0;
|
|
6
|
+
const compare = (a, b) => a.costMs - b.costMs || a.computer.id.localeCompare(b.computer.id);
|
|
7
|
+
|
|
8
|
+
/** Deterministic policy over measured execution envelopes. Advertised GPU names never establish fit. */
|
|
9
|
+
export function planFleetPlacement({ computers, demand, models, links, policy, now }) {
|
|
10
|
+
assert(integer(now) && integer(policy?.maxObservationAgeMs) && policy.maxObservationAgeMs > 0
|
|
11
|
+
&& integer(policy.maxMeasurementAgeMs) && policy.maxMeasurementAgeMs > 0
|
|
12
|
+
&& integer(policy.minResidencyMs) && positive(policy.maxRouteCandidates)
|
|
13
|
+
&& Number.isSafeInteger(policy.maxRouteCandidates) && positive(policy.queuedRequestsPerReplica), 'Explicit placement policy required');
|
|
14
|
+
assert(Array.isArray(computers) && Array.isArray(demand) && Array.isArray(models) && Array.isArray(links), 'Fleet observations required');
|
|
15
|
+
const machines = structuredClone(computers), catalog = structuredClone(models), decisions = [], routes = [], unavailable = [];
|
|
16
|
+
assert(new Set(machines.map(row => row.id)).size === machines.length, 'Duplicate computer');
|
|
17
|
+
const occupied = new Set();
|
|
18
|
+
for (const computer of machines) {
|
|
19
|
+
assert(text(computer.id) && text(computer.participantId) && Array.isArray(computer.measurements)
|
|
20
|
+
&& Array.isArray(computer.preferences?.models) && integer(computer.activeRequests), 'Invalid computer observation');
|
|
21
|
+
const allowed = computer.preferences.enabled === true && (!computer.resident
|
|
22
|
+
|| computer.preferences.models.some(model => model.identity === computer.resident.modelIdentity));
|
|
23
|
+
if (!allowed && computer.activeRequests === 0) {
|
|
24
|
+
occupied.add(computer.id);
|
|
25
|
+
decisions.push({ computerId: computer.id, action: 'stop', reason: 'owner-preferences', resident: computer.resident ?? null });
|
|
26
|
+
continue;
|
|
27
|
+
}
|
|
28
|
+
if (computer.activeRequests > 0 || computer.resident && now - computer.resident.preparedAt < policy.minResidencyMs) {
|
|
29
|
+
occupied.add(computer.id);
|
|
30
|
+
decisions.push({ computerId: computer.id, action: 'retain', reason: computer.activeRequests ? 'active-requests' : 'minimum-residency',
|
|
31
|
+
resident: computer.resident ?? null });
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
function candidates(model, workload, mode, planId = null, stageIndex = null) {
|
|
35
|
+
const results = [];
|
|
36
|
+
for (const computer of machines) {
|
|
37
|
+
const preferences = computer.preferences;
|
|
38
|
+
if (preferences.enabled !== true || !fresh(computer.observedAt, now, policy)
|
|
39
|
+
|| !preferences.models.some(value => key(value) === key(model))) continue;
|
|
40
|
+
for (const measurement of computer.measurements) {
|
|
41
|
+
if (measurement.modelIdentity !== model.identity || measurement.mode !== mode
|
|
42
|
+
|| (measurement.planId ?? null) !== planId || (measurement.stageIndex ?? null) !== stageIndex
|
|
43
|
+
|| measurement.qualified !== true || !text(measurement.receipt)
|
|
44
|
+
|| !digest(computer.softwareIdentity) || measurement.softwareIdentity !== computer.softwareIdentity
|
|
45
|
+
|| !fresh(measurement.observedAt, now, { maxObservationAgeMs: policy.maxMeasurementAgeMs })
|
|
46
|
+
|| !positive(measurement.prefillTokensPerSecond) || !positive(measurement.decodeTokensPerSecond)
|
|
47
|
+
|| !integer(measurement.loadMs) || !integer(measurement.requiredGpuBytes) || !integer(measurement.requiredHostBytes)
|
|
48
|
+
|| !integer(measurement.maxPromptTokens) || workload.promptTokens > measurement.maxPromptTokens
|
|
49
|
+
|| !integer(measurement.maxOutputTokens) || workload.outputTokens > measurement.maxOutputTokens
|
|
50
|
+
|| !integer(measurement.largestBindingBytes)) continue;
|
|
51
|
+
const sameResident = computer.resident?.modelIdentity === model.identity && computer.resident.mode === mode
|
|
52
|
+
&& (computer.resident.planId ?? null) === planId && (computer.resident.stageIndex ?? null) === stageIndex;
|
|
53
|
+
// freeGpuBytes excludes this service's removable resident; explicitly reclaim only its accounted buffers.
|
|
54
|
+
const reclaim = sameResident || computer.activeRequests === 0 ? computer.resident?.gpuBytes ?? 0 : 0;
|
|
55
|
+
const gpuBudget = Math.min(preferences.maxGpuBytes, computer.freeGpuBytes + reclaim);
|
|
56
|
+
const hostBudget = Math.min(preferences.maxHostBytes, computer.freeHostBytes + (sameResident || computer.activeRequests === 0 ? computer.resident?.hostBytes ?? 0 : 0));
|
|
57
|
+
if (![gpuBudget, hostBudget, computer.maxStorageBufferBindingSize].every(integer)
|
|
58
|
+
|| measurement.requiredGpuBytes > gpuBudget || measurement.requiredHostBytes > hostBudget
|
|
59
|
+
|| measurement.largestBindingBytes > computer.maxStorageBufferBindingSize) continue;
|
|
60
|
+
results.push({ computer, measurement, sameResident, mode, planId, stageIndex });
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
return results;
|
|
64
|
+
}
|
|
65
|
+
function assign(candidate, model, reason) {
|
|
66
|
+
occupied.add(candidate.computer.id);
|
|
67
|
+
decisions.push({ computerId: candidate.computer.id, action: candidate.sameResident ? 'retain' : 'prepare', reason,
|
|
68
|
+
modelId: model.id, modelIdentity: model.identity, mode: candidate.mode, planId: candidate.planId,
|
|
69
|
+
stageIndex: candidate.stageIndex, evidence: candidate.measurement.receipt, estimatedMs: candidate.costMs });
|
|
70
|
+
}
|
|
71
|
+
const workloads = structuredClone(demand).sort((a, b) => b.queued - a.queued || a.since - b.since || a.id.localeCompare(b.id));
|
|
72
|
+
for (const workload of workloads) {
|
|
73
|
+
assert(text(workload.id) && digest(workload.identity) && integer(workload.queued) && integer(workload.promptTokens)
|
|
74
|
+
&& integer(workload.outputTokens) && integer(workload.since), 'Invalid measured demand');
|
|
75
|
+
if (workload.queued === 0) continue;
|
|
76
|
+
const model = catalog.find(value => key(value) === key(workload));
|
|
77
|
+
if (!model) { unavailable.push({ modelId: workload.id, reason: 'unknown-exact-model' }); continue; }
|
|
78
|
+
const cost = candidate => ({ ...candidate, costMs: (candidate.sameResident ? 0 : candidate.measurement.loadMs)
|
|
79
|
+
+ 1000 * (workload.promptTokens / candidate.measurement.prefillTokensPerSecond
|
|
80
|
+
+ workload.outputTokens / candidate.measurement.decodeTokensPerSecond) });
|
|
81
|
+
const whole = candidates(model, workload, 'whole-model').map(cost).sort(compare);
|
|
82
|
+
// The product contract prefers feasible whole-model service, including a busy provider's next slot.
|
|
83
|
+
if (whole.length) {
|
|
84
|
+
let count = 0;
|
|
85
|
+
const wanted = Math.ceil(workload.queued / policy.queuedRequestsPerReplica);
|
|
86
|
+
for (const candidate of whole) {
|
|
87
|
+
if (occupied.has(candidate.computer.id)) {
|
|
88
|
+
if (candidate.sameResident && count < wanted) {
|
|
89
|
+
count++;
|
|
90
|
+
routes.push({ modelId: model.id, modelIdentity: model.identity, mode: 'whole-model', computers: [candidate.computer.id],
|
|
91
|
+
estimatedMs: candidate.costMs, busy: candidate.computer.activeRequests > 0 });
|
|
92
|
+
}
|
|
93
|
+
continue;
|
|
94
|
+
}
|
|
95
|
+
if (count >= wanted) break;
|
|
96
|
+
assign(candidate, model, count ? 'queued-demand-replica' : 'fastest-feasible-whole-model'); count++;
|
|
97
|
+
routes.push({ modelId: model.id, modelIdentity: model.identity, mode: 'whole-model', computers: [candidate.computer.id], estimatedMs: candidate.costMs });
|
|
98
|
+
}
|
|
99
|
+
if (!count) unavailable.push({ modelId: model.id, reason: 'eligible-computer-busy' });
|
|
100
|
+
continue;
|
|
101
|
+
}
|
|
102
|
+
let best = null, visited = 0;
|
|
103
|
+
for (const plan of model.plans || []) {
|
|
104
|
+
assert(digest(plan.id) && integer(plan.stageCount) && plan.stageCount >= 2 && integer(plan.activationBytesPerToken), 'Invalid partition plan envelope');
|
|
105
|
+
const stages = Array.from({ length: plan.stageCount }, (_, index) => candidates(model, workload, 'partition', plan.id, index)
|
|
106
|
+
.filter(candidate => !occupied.has(candidate.computer.id) || candidate.sameResident).map(cost).sort(compare));
|
|
107
|
+
const walk = (index, route, costMs) => {
|
|
108
|
+
if (visited++ >= policy.maxRouteCandidates || best && costMs >= best.costMs) return;
|
|
109
|
+
if (index === stages.length) { best = { route, costMs, planId: plan.id }; return; }
|
|
110
|
+
for (const candidate of stages[index]) {
|
|
111
|
+
if (route.some(value => value.computer.id === candidate.computer.id)) continue;
|
|
112
|
+
let transfer = 0;
|
|
113
|
+
if (index > 0) {
|
|
114
|
+
const link = links.find(value => value.from === route[index - 1].computer.id && value.to === candidate.computer.id
|
|
115
|
+
&& fresh(value.observedAt, now, policy) && positive(value.bytesPerSecond) && Number.isFinite(value.latencyMs) && value.latencyMs >= 0);
|
|
116
|
+
if (!link) continue;
|
|
117
|
+
transfer = (workload.outputTokens + 1) * link.latencyMs
|
|
118
|
+
+ 1000 * plan.activationBytesPerToken * (workload.promptTokens + workload.outputTokens) / link.bytesPerSecond;
|
|
119
|
+
}
|
|
120
|
+
walk(index + 1, [...route, candidate], costMs + candidate.costMs + transfer);
|
|
121
|
+
}
|
|
122
|
+
};
|
|
123
|
+
walk(0, [], 0);
|
|
124
|
+
}
|
|
125
|
+
if (!best) { unavailable.push({ modelId: model.id, reason: visited >= policy.maxRouteCandidates ? 'bounded-route-search-exhausted' : 'no-measured-complete-route' }); continue; }
|
|
126
|
+
for (const candidate of best.route) if (!occupied.has(candidate.computer.id)) assign(candidate, model, 'complete-partition-route');
|
|
127
|
+
routes.push({ modelId: model.id, modelIdentity: model.identity, mode: 'partition', planId: best.planId,
|
|
128
|
+
computers: best.route.map(value => value.computer.id), estimatedMs: best.costMs });
|
|
129
|
+
}
|
|
130
|
+
for (const computer of machines) if (!occupied.has(computer.id)) decisions.push({ computerId: computer.id, action: 'retain', reason: 'no-uncovered-demand', resident: computer.resident ?? null });
|
|
131
|
+
return { schema: 'reploid.fleet-placement/v1', at: now, decisions, routes, unavailable };
|
|
132
|
+
}
|
|
@@ -0,0 +1,346 @@
|
|
|
1
|
+
import { hashJson } from '../../packages/reploid/src/artifacts/signatures.js';
|
|
2
|
+
import { assert } from './contract.js';
|
|
3
|
+
import { createAttemptEvidenceSigner, openOutputAcknowledgments } from '../../packages/reploid/src/mesh/partitions/partition-attempt-evidence.js';
|
|
4
|
+
import { partitionAdapterIdentities } from '../../packages/reploid/src/mesh/partitions/partition-contract.js';
|
|
5
|
+
|
|
6
|
+
export function buildFleetRequestOutcome({
|
|
7
|
+
attemptId = null, threadId = null, reservationId = null, executionId = null,
|
|
8
|
+
mode, model, providerId = null, providerIdentity = null, inputDigest,
|
|
9
|
+
phase, dispatched, acknowledged, settlement = null, maximum = 0, recordedAt = Date.now()
|
|
10
|
+
}) {
|
|
11
|
+
const settled = typeof settlement?.settledAt === 'number';
|
|
12
|
+
const terminal = settlement?.status === 'cancelled' || settlement?.status === 'failed';
|
|
13
|
+
const finished = !dispatched
|
|
14
|
+
? {
|
|
15
|
+
status: settlement?.status ?? 'cancelled_queued',
|
|
16
|
+
charge: 0,
|
|
17
|
+
outputTokens: 0,
|
|
18
|
+
settledAt: settlement?.settledAt ?? recordedAt,
|
|
19
|
+
evidenceHash: null
|
|
20
|
+
}
|
|
21
|
+
: settled
|
|
22
|
+
? {
|
|
23
|
+
status: settlement.status ?? 'completed',
|
|
24
|
+
charge: settlement.charge,
|
|
25
|
+
outputTokens: settlement.outputTokens ?? null,
|
|
26
|
+
settledAt: settlement.settledAt,
|
|
27
|
+
evidenceHash: settlement.evidenceHash ?? null
|
|
28
|
+
}
|
|
29
|
+
: null;
|
|
30
|
+
const unknown = !dispatched ? null : settled
|
|
31
|
+
? (mode === 'whole-model' && terminal ? { providerExecutionTokensBeforeStop: 'unmetered' } : null)
|
|
32
|
+
: {
|
|
33
|
+
reservationId,
|
|
34
|
+
executionId,
|
|
35
|
+
providerId,
|
|
36
|
+
providerIdentity,
|
|
37
|
+
providerExecution: 'unresolved',
|
|
38
|
+
heldCredits: maximum
|
|
39
|
+
};
|
|
40
|
+
return Object.freeze({
|
|
41
|
+
schema: 'reploid.request-outcome/v1',
|
|
42
|
+
attemptId,
|
|
43
|
+
threadId,
|
|
44
|
+
reservationId,
|
|
45
|
+
executionId,
|
|
46
|
+
mode,
|
|
47
|
+
model: Object.freeze({ id: model.id, identity: model.identity }),
|
|
48
|
+
providerId,
|
|
49
|
+
providerIdentity,
|
|
50
|
+
inputDigest,
|
|
51
|
+
phase,
|
|
52
|
+
dispatched,
|
|
53
|
+
acknowledged: Object.freeze({
|
|
54
|
+
deltaCount: acknowledged?.deltaCount ?? 0,
|
|
55
|
+
outputCharacters: acknowledged?.outputCharacters ?? 0,
|
|
56
|
+
acknowledgedOutputTokens: acknowledged?.acknowledgedOutputTokens ?? 0
|
|
57
|
+
}),
|
|
58
|
+
finished: finished ? Object.freeze(finished) : null,
|
|
59
|
+
unknown: unknown ? Object.freeze(unknown) : null,
|
|
60
|
+
recordedAt
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
/** Compose credit admission with the native requester's existing disclosure gate. */
|
|
65
|
+
export function createFleetRequester({
|
|
66
|
+
participant, identity, credits, maxOutputTokens,
|
|
67
|
+
onOutcome = () => {}, lookupOutcome = () => null, resolveProviderId = () => null
|
|
68
|
+
}) {
|
|
69
|
+
assert(participant?.generate && identity?.peerId && credits?.reserve && credits?.settle,
|
|
70
|
+
'Requester, signing identity and service-credit owner required');
|
|
71
|
+
const readReservation = id => typeof credits.request === 'function' ? credits.request(id) : credits.call('request', { id });
|
|
72
|
+
const recoverReservation = id => typeof credits.recover === 'function' ? credits.recover(id) : credits.call('recover', { id });
|
|
73
|
+
return Object.freeze({
|
|
74
|
+
async generate(request, controls) {
|
|
75
|
+
request = structuredClone(request);
|
|
76
|
+
assert(typeof controls?.requestApproval === 'function' && controls.signal, 'Explicit disclosure and cancellation required');
|
|
77
|
+
const partition = Boolean(request.model.partition);
|
|
78
|
+
const mode = partition ? 'partition' : 'whole-model';
|
|
79
|
+
const outputLimit = Math.min(maxOutputTokens, request.generationRequest?.maxOutputTokens ?? maxOutputTokens,
|
|
80
|
+
request.model.generation?.maxTokens ?? maxOutputTokens);
|
|
81
|
+
assert(Number.isSafeInteger(outputLimit) && outputLimit > 0, 'Explicit bounded output required');
|
|
82
|
+
if (partition) request.generationRequest = { schema: 'reploid.partition-generation-request/v1',
|
|
83
|
+
usage: 'raw-model-tokens/v1', maxOutputTokens: outputLimit };
|
|
84
|
+
const inputDigest = await hashJson(request.messages);
|
|
85
|
+
let reservation = null, dispatched = false, abandoned = false, acknowledgments = null, routeIdentity = null, observeOutput = null;
|
|
86
|
+
let disclosedProviderId = null, disclosedProviderIdentity = null, deltaCount = 0, outputCharacters = 0;
|
|
87
|
+
const wrapped = { ...controls,
|
|
88
|
+
...(controls.onDelta ? {
|
|
89
|
+
onDelta(value) {
|
|
90
|
+
const chunk = typeof value === 'string' ? value : typeof value?.text === 'string' ? value.text : '';
|
|
91
|
+
deltaCount += 1;
|
|
92
|
+
outputCharacters += chunk.length;
|
|
93
|
+
return controls.onDelta(value);
|
|
94
|
+
}
|
|
95
|
+
} : {}),
|
|
96
|
+
authorizationExpiresAt: () => Math.min(reservation?.deadline ?? Infinity, controls.authorizationExpiresAt?.() ?? Infinity),
|
|
97
|
+
requestApproval: async disclosure => {
|
|
98
|
+
assert(!reservation, 'One provider per request; replacements need explicit retry');
|
|
99
|
+
if (await controls.requestApproval(structuredClone(disclosure)) !== true) return false;
|
|
100
|
+
controls.signal.throwIfAborted();
|
|
101
|
+
const executionId = partition ? request.attemptId : disclosure.requestId;
|
|
102
|
+
const binding = { id: `service:${identity.peerId}:${executionId}`, executionId,
|
|
103
|
+
consumer: identity.peerId, mode, model: { id: request.model.id,
|
|
104
|
+
identity: request.model.identity, adapterIdentities: partitionAdapterIdentities(request.model) },
|
|
105
|
+
inputDigest, maxOutputTokens: outputLimit,
|
|
106
|
+
providers: [{ participantId: typeof disclosure.recipientIdentity === 'string' ? disclosure.recipientIdentity
|
|
107
|
+
: disclosure.recipientIdentity?.participantId ?? disclosure.recipientIdentity?.peerId, weight: 1 }] };
|
|
108
|
+
if (partition) {
|
|
109
|
+
routeIdentity = { schema: 'reploid.partition-attempt/v2', threadId: request.threadId, attemptId: executionId,
|
|
110
|
+
requesterId: identity.peerId, placementGeneration: request.placementGeneration ?? 0, route: disclosure.route };
|
|
111
|
+
const terms = await credits.call('terms', binding.model);
|
|
112
|
+
const plan = terms.model.plans?.find(row => row.id === disclosure.route.planId);
|
|
113
|
+
assert(plan?.creditWeights?.length === disclosure.route.stages.length, 'No accepted credit terms for this partition');
|
|
114
|
+
binding.route = routeIdentity;
|
|
115
|
+
binding.providers = disclosure.route.stages.map((stage, index) => ({ participantId: stage.participantId, weight: plan.creditWeights[index] }));
|
|
116
|
+
acknowledgments = await openOutputAcknowledgments({ signer: createAttemptEvidenceSigner({ identity }),
|
|
117
|
+
reservationId: binding.id, identity: routeIdentity });
|
|
118
|
+
wrapped.charge = acknowledgments.charge;
|
|
119
|
+
wrapped.onOutput = observeOutput = async record => {
|
|
120
|
+
const pair = await acknowledgments.observe(record);
|
|
121
|
+
if (pair) await credits.collect(binding.id, { identity: routeIdentity, receipts: [], acknowledged: pair });
|
|
122
|
+
return pair;
|
|
123
|
+
};
|
|
124
|
+
}
|
|
125
|
+
disclosedProviderId = typeof disclosure.providerId === 'string' ? disclosure.providerId : null;
|
|
126
|
+
disclosedProviderIdentity = binding.providers[0]?.participantId ?? null;
|
|
127
|
+
reservation = await credits.reserve(binding);
|
|
128
|
+
try {
|
|
129
|
+
if (partition) await credits.collect(reservation.id, { identity: routeIdentity, receipts: [], acknowledgment: acknowledgments.charge });
|
|
130
|
+
controls.signal.throwIfAborted();
|
|
131
|
+
// A lost response leaves admission uncertain; never automatically release that hold.
|
|
132
|
+
dispatched = true;
|
|
133
|
+
await credits.dispatch(reservation.id, identity.peerId);
|
|
134
|
+
controls.onReservation?.(reservation.id);
|
|
135
|
+
} catch (error) {
|
|
136
|
+
if (!dispatched) {
|
|
137
|
+
await credits.abandonBeforeDispatch(reservation.id, identity.peerId);
|
|
138
|
+
abandoned = true;
|
|
139
|
+
}
|
|
140
|
+
throw error;
|
|
141
|
+
}
|
|
142
|
+
return true;
|
|
143
|
+
}
|
|
144
|
+
};
|
|
145
|
+
let result, failure;
|
|
146
|
+
try { result = await participant.generate(request, wrapped); }
|
|
147
|
+
catch (error) { failure = error; }
|
|
148
|
+
if (!reservation || !dispatched) {
|
|
149
|
+
const recordedAt = Date.now();
|
|
150
|
+
const outcome = buildFleetRequestOutcome({
|
|
151
|
+
attemptId: request.attemptId ?? null,
|
|
152
|
+
threadId: request.threadId ?? null,
|
|
153
|
+
reservationId: reservation?.id ?? null,
|
|
154
|
+
executionId: reservation?.executionId ?? null,
|
|
155
|
+
mode,
|
|
156
|
+
model: request.model,
|
|
157
|
+
inputDigest,
|
|
158
|
+
phase: 'queued',
|
|
159
|
+
dispatched: false,
|
|
160
|
+
acknowledged: { deltaCount: 0, outputCharacters: 0, acknowledgedOutputTokens: 0 },
|
|
161
|
+
settlement: { status: abandoned ? 'abandoned' : 'cancelled_queued', charge: 0, outputTokens: 0, settledAt: recordedAt },
|
|
162
|
+
maximum: 0,
|
|
163
|
+
recordedAt
|
|
164
|
+
});
|
|
165
|
+
onOutcome(outcome);
|
|
166
|
+
if (failure) {
|
|
167
|
+
if (typeof failure === 'object') failure.disconnect = outcome;
|
|
168
|
+
throw failure;
|
|
169
|
+
}
|
|
170
|
+
throw Error('Output arrived without credit admission');
|
|
171
|
+
}
|
|
172
|
+
let settlement;
|
|
173
|
+
try {
|
|
174
|
+
if (partition) {
|
|
175
|
+
const closing = result?.execution?.settlement ?? failure?.partitionSettlement;
|
|
176
|
+
// A dropped output RPC can recover through the bounded closing response. These are
|
|
177
|
+
// actual final-stage signed outputs, not a token count inferred from the visible answer.
|
|
178
|
+
for (const record of closing?.unacknowledgedOutputs || []) {
|
|
179
|
+
if (record.step >= acknowledgments.state().count) await observeOutput(record);
|
|
180
|
+
}
|
|
181
|
+
const pair = acknowledgments.evidence().acknowledged;
|
|
182
|
+
const evidence = { identity: routeIdentity,
|
|
183
|
+
receipts: (closing?.cleanup || []).flatMap(row => row?.receipt ? [row.receipt] : []),
|
|
184
|
+
acknowledgment: pair?.acknowledgment ?? acknowledgments.charge,
|
|
185
|
+
...(pair ? { acknowledgedReceipt: pair.receipt } : {}) };
|
|
186
|
+
await credits.collect(reservation.id, evidence);
|
|
187
|
+
if (result && evidence.receipts.length === routeIdentity.route.stages.length) {
|
|
188
|
+
settlement = await credits.settle(reservation.id, evidence);
|
|
189
|
+
} else settlement = await credits.fence(reservation.id, identity.peerId);
|
|
190
|
+
} else if (result?.execution?.receipt) settlement = await credits.settle(reservation.id, result.execution.receipt);
|
|
191
|
+
else if (failure?.generationTerminal) settlement = await credits.collect(reservation.id, { terminal: failure.generationTerminal });
|
|
192
|
+
else settlement = await credits.fence(reservation.id, identity.peerId);
|
|
193
|
+
} catch (error) {
|
|
194
|
+
settlement = { id: reservation.id, status: 'pending', error: error.message };
|
|
195
|
+
// Sealing admission is durable even if collection/settlement could not reach the service.
|
|
196
|
+
await credits.fence(reservation.id, identity.peerId).catch(() => {});
|
|
197
|
+
}
|
|
198
|
+
const completed = typeof settlement?.settledAt === 'number' && !['cancelled', 'failed'].includes(settlement?.status);
|
|
199
|
+
const acknowledgedOutputTokens = partition
|
|
200
|
+
? (acknowledgments?.state().count ?? 0)
|
|
201
|
+
: completed ? (settlement.outputTokens ?? result?.usage?.outputTokens ?? 0) : 0;
|
|
202
|
+
const outcome = buildFleetRequestOutcome({
|
|
203
|
+
attemptId: request.attemptId ?? null,
|
|
204
|
+
threadId: request.threadId ?? null,
|
|
205
|
+
reservationId: reservation.id,
|
|
206
|
+
executionId: reservation.executionId,
|
|
207
|
+
mode,
|
|
208
|
+
model: request.model,
|
|
209
|
+
providerId: disclosedProviderId,
|
|
210
|
+
providerIdentity: disclosedProviderIdentity,
|
|
211
|
+
inputDigest,
|
|
212
|
+
phase: 'in-flight',
|
|
213
|
+
dispatched: true,
|
|
214
|
+
acknowledged: { deltaCount, outputCharacters, acknowledgedOutputTokens },
|
|
215
|
+
settlement,
|
|
216
|
+
maximum: reservation.maximum,
|
|
217
|
+
recordedAt: Date.now()
|
|
218
|
+
});
|
|
219
|
+
onOutcome(outcome);
|
|
220
|
+
if (failure) {
|
|
221
|
+
if (typeof failure === 'object') {
|
|
222
|
+
failure.serviceCredit = settlement;
|
|
223
|
+
failure.disconnect = outcome;
|
|
224
|
+
}
|
|
225
|
+
throw failure;
|
|
226
|
+
}
|
|
227
|
+
return { ...result, serviceCredit: settlement };
|
|
228
|
+
},
|
|
229
|
+
async recover({ reservationId, messages, providerId }, controls, recoverExecution = participant.recoverGeneration) {
|
|
230
|
+
assert(typeof reservationId === 'string' && reservationId.length > 0 && Array.isArray(messages) && messages.length > 0
|
|
231
|
+
&& controls?.signal && typeof recoverExecution === 'function', 'Bound reservation, original messages and cancellation required');
|
|
232
|
+
controls.signal.throwIfAborted();
|
|
233
|
+
let row;
|
|
234
|
+
try { row = await readReservation(reservationId); }
|
|
235
|
+
catch (error) {
|
|
236
|
+
if (/Unknown credit reservation|Not a participant/i.test(error?.message || '')) {
|
|
237
|
+
throw Object.assign(Error('Unknown credit reservation'), { status: 404 });
|
|
238
|
+
}
|
|
239
|
+
throw error;
|
|
240
|
+
}
|
|
241
|
+
if (!row) throw Object.assign(Error('Unknown credit reservation'), { status: 404 });
|
|
242
|
+
if (row.binding?.consumer !== identity.peerId) {
|
|
243
|
+
throw Object.assign(Error('Reservation belongs to another requester'), { status: 403 });
|
|
244
|
+
}
|
|
245
|
+
const inputDigest = await hashJson(messages);
|
|
246
|
+
if (inputDigest !== row.binding.inputDigest) {
|
|
247
|
+
throw Object.assign(Error('Recovery input differs from the accepted request'), { status: 409 });
|
|
248
|
+
}
|
|
249
|
+
const prior = lookupOutcome(reservationId);
|
|
250
|
+
const emit = (dispatched, settlement, extraTokens = null, resolvedPeer = null) => {
|
|
251
|
+
const completed = typeof settlement?.settledAt === 'number' && !['cancelled', 'failed'].includes(settlement?.status);
|
|
252
|
+
const outcome = buildFleetRequestOutcome({
|
|
253
|
+
attemptId: prior?.attemptId ?? null,
|
|
254
|
+
threadId: prior?.threadId ?? null,
|
|
255
|
+
reservationId,
|
|
256
|
+
executionId: row.binding.executionId,
|
|
257
|
+
mode: row.binding.mode,
|
|
258
|
+
model: row.binding.model,
|
|
259
|
+
providerId: resolvedPeer ?? prior?.providerId ?? null,
|
|
260
|
+
providerIdentity: row.binding.providers[0]?.participantId ?? null,
|
|
261
|
+
inputDigest,
|
|
262
|
+
phase: dispatched ? 'in-flight' : 'queued',
|
|
263
|
+
dispatched,
|
|
264
|
+
acknowledged: {
|
|
265
|
+
deltaCount: prior?.acknowledged?.deltaCount ?? 0,
|
|
266
|
+
outputCharacters: prior?.acknowledged?.outputCharacters ?? 0,
|
|
267
|
+
acknowledgedOutputTokens: extraTokens ?? (completed ? (settlement.outputTokens ?? 0) : (prior?.acknowledged?.acknowledgedOutputTokens ?? 0))
|
|
268
|
+
},
|
|
269
|
+
settlement,
|
|
270
|
+
maximum: dispatched ? row.binding.maximum : 0,
|
|
271
|
+
recordedAt: Date.now()
|
|
272
|
+
});
|
|
273
|
+
onOutcome(outcome);
|
|
274
|
+
return outcome;
|
|
275
|
+
};
|
|
276
|
+
if (row.state === 'abandoned') {
|
|
277
|
+
const outcome = emit(false, { status: 'abandoned', charge: 0, outputTokens: 0, settledAt: row.binding.createdAt });
|
|
278
|
+
return { status: 'abandoned', outcome };
|
|
279
|
+
}
|
|
280
|
+
if (row.state === 'reserved') {
|
|
281
|
+
await credits.abandonBeforeDispatch(reservationId, identity.peerId);
|
|
282
|
+
const outcome = emit(false, { status: 'abandoned', charge: 0, outputTokens: 0, settledAt: Date.now() });
|
|
283
|
+
return { status: 'abandoned', outcome };
|
|
284
|
+
}
|
|
285
|
+
if (row.state === 'settled') {
|
|
286
|
+
const outcome = emit(true, row.outcome);
|
|
287
|
+
return { status: row.outcome.status ?? 'completed', serviceCredit: row.outcome, outcome };
|
|
288
|
+
}
|
|
289
|
+
if (row.binding.mode === 'partition') {
|
|
290
|
+
const settled = await recoverReservation(reservationId);
|
|
291
|
+
const outcome = emit(true, settled);
|
|
292
|
+
return { status: typeof settled?.settledAt === 'number' ? (settled.status ?? 'completed') : 'unresolved', serviceCredit: settled, outcome };
|
|
293
|
+
}
|
|
294
|
+
const fenced = await credits.fence(reservationId, identity.peerId);
|
|
295
|
+
if (typeof fenced?.settledAt === 'number') {
|
|
296
|
+
const outcome = emit(true, fenced);
|
|
297
|
+
return { status: fenced.status ?? 'completed', serviceCredit: fenced, outcome };
|
|
298
|
+
}
|
|
299
|
+
const targetProviderId = providerId ?? prior?.providerId ?? await resolveProviderId(reservationId, row.binding);
|
|
300
|
+
if (typeof targetProviderId !== 'string' || !targetProviderId) {
|
|
301
|
+
throw Object.assign(Error('Recovery peer required'), { status: 409 });
|
|
302
|
+
}
|
|
303
|
+
const request = {
|
|
304
|
+
threadId: prior?.threadId ?? `recover-${row.binding.executionId}`,
|
|
305
|
+
attemptId: prior?.attemptId ?? row.binding.executionId,
|
|
306
|
+
model: {
|
|
307
|
+
id: row.binding.model.id,
|
|
308
|
+
identity: row.binding.model.identity,
|
|
309
|
+
adapters: (row.binding.model.adapterIdentities || []).map(adapterId => ({
|
|
310
|
+
identity: adapterId, baseModelIdentity: row.binding.model.identity
|
|
311
|
+
}))
|
|
312
|
+
},
|
|
313
|
+
messages: structuredClone(messages)
|
|
314
|
+
};
|
|
315
|
+
const reference = {
|
|
316
|
+
requestId: row.binding.executionId,
|
|
317
|
+
providerId: targetProviderId,
|
|
318
|
+
providerIdentity: row.binding.providers[0].participantId
|
|
319
|
+
};
|
|
320
|
+
const recovered = await recoverExecution(request, reference, { signal: controls.signal });
|
|
321
|
+
controls.signal.throwIfAborted();
|
|
322
|
+
if (recovered?.status === 'unresolved') {
|
|
323
|
+
const outcome = emit(true, { id: reservationId, status: 'pending' }, null, targetProviderId);
|
|
324
|
+
return {
|
|
325
|
+
status: 'unresolved',
|
|
326
|
+
serviceCredit: { id: reservationId, status: 'pending', heldCredits: row.binding.maximum },
|
|
327
|
+
outcome
|
|
328
|
+
};
|
|
329
|
+
}
|
|
330
|
+
if (recovered?.status === 'completed') {
|
|
331
|
+
const settled = await credits.settle(reservationId, recovered.result.execution.receipt);
|
|
332
|
+
const outcome = emit(true, settled, settled.outputTokens, targetProviderId);
|
|
333
|
+
return {
|
|
334
|
+
status: 'completed',
|
|
335
|
+
...recovered.result,
|
|
336
|
+
execution: { ...recovered.result.execution, serviceCredit: settled },
|
|
337
|
+
serviceCredit: settled,
|
|
338
|
+
outcome
|
|
339
|
+
};
|
|
340
|
+
}
|
|
341
|
+
const settled = await credits.collect(reservationId, { terminal: recovered.terminal });
|
|
342
|
+
const outcome = emit(true, settled, 0, targetProviderId);
|
|
343
|
+
return { status: recovered.terminal.status, serviceCredit: settled, outcome };
|
|
344
|
+
}
|
|
345
|
+
});
|
|
346
|
+
}
|