deepline 0.3.283 → 0.3.285
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/bundling-sources/sdk/src/release.ts +1 -1
- package/dist/bundling-sources/shared_libs/play-runtime/activity-observation.ts +26 -2
- package/dist/bundling-sources/shared_libs/play-runtime/batch-runtime.ts +7 -0
- package/dist/bundling-sources/shared_libs/play-runtime/context.ts +817 -2236
- package/dist/bundling-sources/shared_libs/play-runtime/ctx-types.ts +19 -6
- package/dist/bundling-sources/shared_libs/play-runtime/durable-receipt-execution.ts +127 -789
- package/dist/bundling-sources/shared_libs/play-runtime/run-ledger.ts +180 -14
- package/dist/bundling-sources/shared_libs/play-runtime/runner-app/index.ts +1 -0
- package/dist/bundling-sources/shared_libs/play-runtime/runner-backends/backends/daytona.ts +3 -22
- package/dist/bundling-sources/shared_libs/play-runtime/runtime-incident-drills.ts +26 -0
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/absurd-runtime-worker.ts +50 -8
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/postgres-progress.ts +122 -18
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/postgres.ts +218 -41
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backends/runtime-sandbox-capacity.ts +20 -2
- package/dist/bundling-sources/shared_libs/play-runtime/test-runtime-seams.ts +13 -2
- package/dist/bundling-sources/shared_libs/play-runtime/tool-call/receipt-cohort.ts +181 -326
- package/dist/bundling-sources/shared_libs/play-runtime/tool-call/runtime-step-receipts-adapter.ts +38 -151
- package/dist/bundling-sources/shared_libs/play-runtime/worker-api-types.ts +8 -0
- package/dist/bundling-sources/shared_libs/security/secret-redaction.ts +89 -1
- package/dist/cli/index.js +28 -3
- package/dist/cli/index.mjs +28 -3
- package/dist/{client-B0AjIkgb.d.mts → client-Cl-zj2oS.d.mts} +4 -0
- package/dist/{client-BCXQ_Ftc.d.ts → client-T2jVnZrq.d.ts} +4 -0
- package/dist/index.d.mts +2 -2
- package/dist/index.d.ts +2 -2
- package/dist/index.js +25 -1
- package/dist/index.mjs +25 -1
- package/dist/install-integrity.json +2 -2
- package/dist/plays/run-request.d.mts +1 -1
- package/dist/plays/run-request.d.ts +1 -1
- package/dist/release.d.mts +1 -1
- package/dist/release.d.ts +1 -1
- package/dist/release.js +1 -1
- package/dist/release.mjs +1 -1
- package/package.json +1 -1
|
@@ -37,7 +37,7 @@ export type ToolCallReceiptCohortStore = {
|
|
|
37
37
|
*
|
|
38
38
|
* Deliberately returns nothing. A completed-result insert is immutable, so a
|
|
39
39
|
* caller that already executed keeps its own result whether or not it won
|
|
40
|
-
* the insert
|
|
40
|
+
* the insert. Materializing the stored rows here
|
|
41
41
|
* only bought a second deserialize-and-wrap of every result, inside the
|
|
42
42
|
* delivery interval a row is blocked on.
|
|
43
43
|
*/
|
|
@@ -46,6 +46,8 @@ export type ToolCallReceiptCohortStore = {
|
|
|
46
46
|
ownerAttempt: number;
|
|
47
47
|
receipts: Array<{
|
|
48
48
|
resultReceiptKey: string;
|
|
49
|
+
/** Successful invocations represented by this coalesced cache write. */
|
|
50
|
+
memberIds?: readonly string[];
|
|
49
51
|
lease: ToolCallReceiptLease;
|
|
50
52
|
result: ToolExecuteResult;
|
|
51
53
|
}>;
|
|
@@ -149,250 +151,210 @@ export function createToolCallReceiptCohortJob(input: {
|
|
|
149
151
|
'Tool Call Receipt Cohort requires at least one member.',
|
|
150
152
|
);
|
|
151
153
|
}
|
|
152
|
-
|
|
153
|
-
const
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
154
|
+
const normalized = new Map<string, ToolCallReceiptCohortMember>();
|
|
155
|
+
for (const member of members) {
|
|
156
|
+
const memberId = required(member.memberId, 'member id');
|
|
157
|
+
if (normalized.has(memberId)) {
|
|
158
|
+
throw new Error(
|
|
159
|
+
`Duplicate Tool Call Receipt Cohort member ${memberId}.`,
|
|
160
|
+
);
|
|
161
|
+
}
|
|
162
|
+
normalized.set(memberId, {
|
|
163
|
+
...member,
|
|
164
|
+
memberId,
|
|
165
|
+
resultReceiptKey: required(
|
|
166
|
+
member.resultReceiptKey,
|
|
167
|
+
'result receipt key',
|
|
168
|
+
),
|
|
169
|
+
});
|
|
170
|
+
}
|
|
171
|
+
const cacheReadKeys = [
|
|
172
|
+
...new Set(
|
|
173
|
+
[...normalized.values()]
|
|
174
|
+
.filter((member) => !member.force)
|
|
175
|
+
.map((member) => member.resultReceiptKey),
|
|
176
|
+
),
|
|
177
|
+
];
|
|
178
|
+
const cached = new Map(
|
|
179
|
+
(cacheReadKeys.length > 0
|
|
166
180
|
? await input.store.read({ resultReceiptKeys: cacheReadKeys })
|
|
167
|
-
: []
|
|
181
|
+
: []
|
|
182
|
+
).map((record) => [record.resultReceiptKey, record]),
|
|
168
183
|
);
|
|
169
184
|
const states = new Map<string, ToolCallReceiptCohortMemberState>();
|
|
170
|
-
const
|
|
171
|
-
const
|
|
172
|
-
|
|
173
|
-
let publicationFlushScheduled = false;
|
|
185
|
+
const owned = new Map<string, OwnedMember>();
|
|
186
|
+
const pending: Publication[] = [];
|
|
187
|
+
let flushScheduled = false;
|
|
174
188
|
|
|
175
|
-
const
|
|
176
|
-
|
|
177
|
-
|
|
189
|
+
for (const member of normalized.values()) {
|
|
190
|
+
const record = !member.force && cached.get(member.resultReceiptKey);
|
|
191
|
+
if (record) {
|
|
178
192
|
states.set(member.memberId, {
|
|
179
193
|
kind: 'completed',
|
|
180
|
-
result,
|
|
181
|
-
source: '
|
|
194
|
+
result: record.result,
|
|
195
|
+
source: 'cache',
|
|
182
196
|
});
|
|
183
|
-
}
|
|
184
|
-
};
|
|
185
|
-
const rejectGroup = (group: Group, error: unknown) => {
|
|
186
|
-
deferredFor(group, deferred).reject(error);
|
|
187
|
-
};
|
|
188
|
-
|
|
189
|
-
for (const group of groups.values()) {
|
|
190
|
-
const cached = records.get(group.resultReceiptKey);
|
|
191
|
-
if (!group.force && cached) {
|
|
192
|
-
for (const member of group.members) {
|
|
193
|
-
states.set(member.memberId, {
|
|
194
|
-
kind: 'completed',
|
|
195
|
-
result: cached.result,
|
|
196
|
-
source: 'cache',
|
|
197
|
-
});
|
|
198
|
-
}
|
|
199
197
|
continue;
|
|
200
198
|
}
|
|
201
199
|
const lease = {
|
|
202
200
|
leaseId: COMPLETED_RECEIPT_CACHE_INSERT_LEASE_ID,
|
|
203
201
|
ownerRunId,
|
|
204
202
|
};
|
|
205
|
-
const
|
|
206
|
-
owned.set(
|
|
207
|
-
|
|
208
|
-
states.set(ownerMember.memberId, {
|
|
203
|
+
const ownedMember = { member, lease };
|
|
204
|
+
owned.set(member.memberId, ownedMember);
|
|
205
|
+
states.set(member.memberId, {
|
|
209
206
|
kind: 'owned',
|
|
210
|
-
resultReceiptKey:
|
|
207
|
+
resultReceiptKey: member.resultReceiptKey,
|
|
211
208
|
lease,
|
|
212
|
-
receipts: receiptView(
|
|
209
|
+
receipts: receiptView(ownedMember),
|
|
213
210
|
});
|
|
214
|
-
for (const follower of group.members.slice(1)) {
|
|
215
|
-
states.set(follower.memberId, {
|
|
216
|
-
kind: 'following',
|
|
217
|
-
resultReceiptKey: group.resultReceiptKey,
|
|
218
|
-
ownerMemberId: ownerMember.memberId,
|
|
219
|
-
result: deferredFor(group, deferred).promise,
|
|
220
|
-
});
|
|
221
|
-
}
|
|
222
211
|
}
|
|
223
212
|
|
|
224
|
-
const
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
// write so a publication can be settled the moment ITS receipts are
|
|
230
|
-
// durable rather than when the whole wave is. A lease lost between the
|
|
231
|
-
// queue and this flush is a pre-write fault of the wave, so it still
|
|
232
|
-
// rejects every publication in it.
|
|
233
|
-
const claims: PublicationClaim[] = [];
|
|
234
|
-
const requestedByKey = new Map<string, RequestedReceipt>();
|
|
235
|
-
try {
|
|
236
|
-
for (const publication of pending) {
|
|
237
|
-
const entries = ownedEntries(publication.entries, states, owned);
|
|
238
|
-
for (const entry of entries) {
|
|
239
|
-
const key = entry.group.resultReceiptKey;
|
|
240
|
-
if (!requestedByKey.has(key)) requestedByKey.set(key, entry);
|
|
241
|
-
}
|
|
242
|
-
claims.push({
|
|
243
|
-
publication,
|
|
244
|
-
resultReceiptKeys: entries.map(
|
|
245
|
-
(entry) => entry.group.resultReceiptKey,
|
|
246
|
-
),
|
|
247
|
-
settled: false,
|
|
248
|
-
});
|
|
249
|
-
}
|
|
250
|
-
} catch (error) {
|
|
251
|
-
for (const publication of pending) publication.reject(error);
|
|
252
|
-
return;
|
|
253
|
-
}
|
|
254
|
-
const requested = [...requestedByKey.values()];
|
|
255
|
-
const settledByKey = new Map<string, ReceiptSettlement>();
|
|
256
|
-
const results = new Map<string, ToolExecuteResult>();
|
|
213
|
+
const assertOwned = (memberId: string): OwnedMember => {
|
|
214
|
+
const current = owned.get(memberId);
|
|
215
|
+
if (!current) throw new ToolCallReceiptLeaseLostError(memberId);
|
|
216
|
+
return current;
|
|
217
|
+
};
|
|
257
218
|
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
);
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
// nothing about the other receipts of the same wave.
|
|
274
|
-
claim.publication.reject(failed.error);
|
|
275
|
-
continue;
|
|
276
|
-
}
|
|
277
|
-
claim.publication.resolve(
|
|
278
|
-
new Map(
|
|
279
|
-
claim.publication.entries.flatMap((entry) => {
|
|
280
|
-
const result = results.get(entry.memberId);
|
|
281
|
-
return result ? [[entry.memberId, result] as const] : [];
|
|
282
|
-
}),
|
|
283
|
-
),
|
|
284
|
-
);
|
|
219
|
+
const flush = async (): Promise<void> => {
|
|
220
|
+
flushScheduled = false;
|
|
221
|
+
const publications = pending.splice(0);
|
|
222
|
+
const writes = new Map<string, PublicationEntry>();
|
|
223
|
+
const entriesByKey = new Map<string, PublicationEntry[]>();
|
|
224
|
+
const publicationsByKey = new Map<string, Publication[]>();
|
|
225
|
+
for (const publication of publications) {
|
|
226
|
+
for (const entry of publication.entries) {
|
|
227
|
+
const key = entry.member.resultReceiptKey;
|
|
228
|
+
// Coalesce cache writes only. Every member already executed and
|
|
229
|
+
// retains its own result, even when this insert loses a race.
|
|
230
|
+
if (!writes.has(key)) writes.set(key, entry);
|
|
231
|
+
const matching = entriesByKey.get(key) ?? [];
|
|
232
|
+
matching.push(entry);
|
|
233
|
+
entriesByKey.set(key, matching);
|
|
285
234
|
}
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
if (
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
235
|
+
for (const key of publication.remaining) {
|
|
236
|
+
const matching = publicationsByKey.get(key) ?? [];
|
|
237
|
+
matching.push(publication);
|
|
238
|
+
publicationsByKey.set(key, matching);
|
|
239
|
+
}
|
|
240
|
+
}
|
|
241
|
+
const settled = new Set<string>();
|
|
242
|
+
const settle = (settlement: ToolCallReceiptCohortSettlement) => {
|
|
243
|
+
if (
|
|
244
|
+
!writes.has(settlement.resultReceiptKey) ||
|
|
245
|
+
settled.has(settlement.resultReceiptKey)
|
|
246
|
+
)
|
|
247
|
+
return;
|
|
248
|
+
settled.add(settlement.resultReceiptKey);
|
|
249
|
+
const matching = entriesByKey.get(settlement.resultReceiptKey) ?? [];
|
|
301
250
|
input.timings?.onCompleteSettled?.({
|
|
302
|
-
memberIds:
|
|
251
|
+
memberIds: matching.map((entry) => entry.member.memberId),
|
|
303
252
|
atMs: Date.now(),
|
|
304
253
|
});
|
|
305
|
-
if (
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
for (const member of entry.group.members) {
|
|
314
|
-
results.set(member.memberId, entry.result);
|
|
254
|
+
if (settlement.status === 'stored') {
|
|
255
|
+
for (const entry of matching) {
|
|
256
|
+
owned.delete(entry.member.memberId);
|
|
257
|
+
states.set(entry.member.memberId, {
|
|
258
|
+
kind: 'completed',
|
|
259
|
+
result: entry.result,
|
|
260
|
+
source: 'owner',
|
|
261
|
+
});
|
|
315
262
|
}
|
|
316
263
|
}
|
|
317
|
-
|
|
264
|
+
for (const publication of publicationsByKey.get(
|
|
265
|
+
settlement.resultReceiptKey,
|
|
266
|
+
) ?? []) {
|
|
267
|
+
publication.remaining.delete(settlement.resultReceiptKey);
|
|
268
|
+
if (settlement.status === 'failed')
|
|
269
|
+
publication.failure = settlement;
|
|
270
|
+
if (publication.remaining.size > 0) continue;
|
|
271
|
+
if (publication.failure)
|
|
272
|
+
publication.reject(publication.failure.error);
|
|
273
|
+
else
|
|
274
|
+
publication.resolve(
|
|
275
|
+
new Map(
|
|
276
|
+
publication.entries.map((entry) => [
|
|
277
|
+
entry.member.memberId,
|
|
278
|
+
entry.result,
|
|
279
|
+
]),
|
|
280
|
+
),
|
|
281
|
+
);
|
|
282
|
+
}
|
|
318
283
|
};
|
|
319
|
-
|
|
320
284
|
try {
|
|
321
285
|
await input.store.complete({
|
|
322
286
|
ownerRunId,
|
|
323
287
|
ownerAttempt: owner.attempt,
|
|
324
|
-
receipts:
|
|
325
|
-
resultReceiptKey:
|
|
326
|
-
|
|
327
|
-
|
|
288
|
+
receipts: [...writes.values()].map((entry) => ({
|
|
289
|
+
resultReceiptKey: entry.member.resultReceiptKey,
|
|
290
|
+
memberIds: (
|
|
291
|
+
entriesByKey.get(entry.member.resultReceiptKey) ?? []
|
|
292
|
+
).map((member) => member.member.memberId),
|
|
293
|
+
lease: entry.lease,
|
|
294
|
+
result: entry.result,
|
|
328
295
|
})),
|
|
329
|
-
onReceiptSettled:
|
|
330
|
-
settleReceipt(
|
|
331
|
-
settlement.resultReceiptKey,
|
|
332
|
-
settlement.status === 'failed'
|
|
333
|
-
? { failed: true, error: settlement.error }
|
|
334
|
-
: { failed: false, error: undefined },
|
|
335
|
-
),
|
|
296
|
+
onReceiptSettled: settle,
|
|
336
297
|
});
|
|
337
|
-
for (const
|
|
338
|
-
|
|
339
|
-
failed: false,
|
|
340
|
-
error: undefined,
|
|
341
|
-
});
|
|
298
|
+
for (const resultReceiptKey of writes.keys()) {
|
|
299
|
+
settle({ resultReceiptKey, status: 'stored' });
|
|
342
300
|
}
|
|
343
301
|
} catch (error) {
|
|
344
|
-
for (const
|
|
345
|
-
|
|
346
|
-
failed: true,
|
|
347
|
-
error,
|
|
348
|
-
});
|
|
302
|
+
for (const resultReceiptKey of writes.keys()) {
|
|
303
|
+
settle({ resultReceiptKey, status: 'failed', error });
|
|
349
304
|
}
|
|
350
305
|
}
|
|
351
306
|
};
|
|
352
307
|
|
|
353
|
-
const publish =
|
|
308
|
+
const publish = (
|
|
354
309
|
entries: Array<{ memberId: string; result: ToolExecuteResult }>,
|
|
355
310
|
): Promise<Map<string, ToolExecuteResult>> => {
|
|
356
|
-
|
|
357
|
-
|
|
358
|
-
// never waits for or coordinates another run's provider call.
|
|
359
|
-
if (ownedEntries(entries, states, owned).length === 0) {
|
|
360
|
-
return new Map();
|
|
361
|
-
}
|
|
362
|
-
return await new Promise<Map<string, ToolExecuteResult>>(
|
|
311
|
+
if (entries.length === 0) return Promise.resolve(new Map());
|
|
312
|
+
const publication = new Promise<Map<string, ToolExecuteResult>>(
|
|
363
313
|
(resolve, reject) => {
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
314
|
+
const publications = entries.map((entry) => ({
|
|
315
|
+
...assertOwned(entry.memberId),
|
|
316
|
+
result: entry.result,
|
|
317
|
+
}));
|
|
318
|
+
pending.push({
|
|
319
|
+
entries: publications,
|
|
320
|
+
resolve,
|
|
321
|
+
reject,
|
|
322
|
+
remaining: new Set(
|
|
323
|
+
publications.map((entry) => entry.member.resultReceiptKey),
|
|
324
|
+
),
|
|
369
325
|
});
|
|
326
|
+
if (!flushScheduled) {
|
|
327
|
+
flushScheduled = true;
|
|
328
|
+
setImmediate(() => {
|
|
329
|
+
void flush();
|
|
330
|
+
});
|
|
331
|
+
}
|
|
370
332
|
},
|
|
371
333
|
);
|
|
334
|
+
// Siblings can settle while a caller is still awaiting another one.
|
|
335
|
+
// Observe rejection now, but return the original rejecting promise.
|
|
336
|
+
void publication.catch(() => undefined);
|
|
337
|
+
return publication;
|
|
372
338
|
};
|
|
373
339
|
|
|
374
|
-
function receiptView(
|
|
375
|
-
|
|
376
|
-
groupOwned: OwnedGroup,
|
|
377
|
-
): ToolResultReceipts {
|
|
378
|
-
const { group, lease } = groupOwned;
|
|
340
|
+
function receiptView(current: OwnedMember): ToolResultReceipts {
|
|
341
|
+
const { member, lease } = current;
|
|
379
342
|
const assert = (candidate: {
|
|
380
343
|
resultReceiptKey: string;
|
|
381
|
-
lease
|
|
344
|
+
lease?: ToolCallReceiptLease;
|
|
382
345
|
}) => {
|
|
383
346
|
if (
|
|
384
|
-
candidate.resultReceiptKey !==
|
|
385
|
-
!sameLease(candidate.lease, lease)
|
|
347
|
+
candidate.resultReceiptKey !== member.resultReceiptKey ||
|
|
348
|
+
(candidate.lease && !sameLease(candidate.lease, lease))
|
|
386
349
|
) {
|
|
387
350
|
throw new ToolCallReceiptLeaseLostError(candidate.resultReceiptKey);
|
|
388
351
|
}
|
|
352
|
+
assertOwned(member.memberId);
|
|
389
353
|
};
|
|
390
354
|
return {
|
|
391
355
|
async claim(candidate) {
|
|
392
|
-
|
|
393
|
-
|
|
394
|
-
candidate.ownerRunId !== ownerRunId
|
|
395
|
-
) {
|
|
356
|
+
assert(candidate);
|
|
357
|
+
if (candidate.ownerRunId !== ownerRunId) {
|
|
396
358
|
throw new ToolCallReceiptLeaseLostError(
|
|
397
359
|
candidate.resultReceiptKey,
|
|
398
360
|
);
|
|
@@ -400,34 +362,31 @@ export function createToolCallReceiptCohortJob(input: {
|
|
|
400
362
|
return { kind: 'owned', lease };
|
|
401
363
|
},
|
|
402
364
|
async recover(candidate) {
|
|
403
|
-
|
|
404
|
-
|
|
405
|
-
|
|
406
|
-
) {
|
|
407
|
-
throw new ToolCallReceiptLeaseLostError(
|
|
408
|
-
candidate.resultReceiptKey,
|
|
409
|
-
);
|
|
410
|
-
}
|
|
411
|
-
return await deferredFor(group, deferred).promise;
|
|
365
|
+
throw new Error(
|
|
366
|
+
`An independently executing Tool Call cannot follow ${candidate.resultReceiptKey}.`,
|
|
367
|
+
);
|
|
412
368
|
},
|
|
413
369
|
async heartbeat(candidate) {
|
|
414
370
|
assert(candidate);
|
|
415
|
-
return
|
|
371
|
+
return 'active';
|
|
416
372
|
},
|
|
417
|
-
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
373
|
+
complete(candidate) {
|
|
374
|
+
const completion = (async () => {
|
|
375
|
+
assert(candidate);
|
|
376
|
+
const result = candidate.result;
|
|
377
|
+
await publish([{ memberId: member.memberId, result }]);
|
|
378
|
+
return {
|
|
379
|
+
kind: 'stored' as const,
|
|
380
|
+
result,
|
|
381
|
+
};
|
|
382
|
+
})();
|
|
383
|
+
// The member result wraps the queued promise; guard this promise too.
|
|
384
|
+
void completion.catch(() => undefined);
|
|
385
|
+
return completion;
|
|
426
386
|
},
|
|
427
387
|
async fail(candidate) {
|
|
428
388
|
assert(candidate);
|
|
429
|
-
owned.delete(
|
|
430
|
-
rejectGroup(group, candidate.error);
|
|
389
|
+
owned.delete(member.memberId);
|
|
431
390
|
},
|
|
432
391
|
};
|
|
433
392
|
}
|
|
@@ -435,26 +394,17 @@ export function createToolCallReceiptCohortJob(input: {
|
|
|
435
394
|
return {
|
|
436
395
|
member(memberId) {
|
|
437
396
|
const state = states.get(memberId);
|
|
438
|
-
if (!state)
|
|
397
|
+
if (!state)
|
|
439
398
|
throw new Error(
|
|
440
399
|
`Unknown Tool Call Receipt Cohort member ${memberId}.`,
|
|
441
400
|
);
|
|
442
|
-
}
|
|
443
401
|
return state;
|
|
444
402
|
},
|
|
445
403
|
complete: publish,
|
|
446
404
|
async fail(entries) {
|
|
447
405
|
for (const entry of entries) {
|
|
448
|
-
|
|
449
|
-
|
|
450
|
-
throw new ToolCallReceiptLeaseLostError(entry.memberId);
|
|
451
|
-
}
|
|
452
|
-
const groupOwned = owned.get(state.resultReceiptKey);
|
|
453
|
-
if (!groupOwned) {
|
|
454
|
-
throw new ToolCallReceiptLeaseLostError(state.resultReceiptKey);
|
|
455
|
-
}
|
|
456
|
-
owned.delete(state.resultReceiptKey);
|
|
457
|
-
rejectGroup(groupOwned.group, entry.error);
|
|
406
|
+
assertOwned(entry.memberId);
|
|
407
|
+
owned.delete(entry.memberId);
|
|
458
408
|
}
|
|
459
409
|
},
|
|
460
410
|
async heartbeat() {
|
|
@@ -469,113 +419,19 @@ export function createToolCallReceiptCohortJob(input: {
|
|
|
469
419
|
};
|
|
470
420
|
}
|
|
471
421
|
|
|
472
|
-
type
|
|
473
|
-
|
|
474
|
-
|
|
475
|
-
force: boolean;
|
|
422
|
+
type OwnedMember = {
|
|
423
|
+
member: ToolCallReceiptCohortMember;
|
|
424
|
+
lease: ToolCallReceiptLease;
|
|
476
425
|
};
|
|
477
|
-
|
|
478
|
-
type
|
|
479
|
-
|
|
480
|
-
type Deferred<T> = {
|
|
481
|
-
promise: Promise<T>;
|
|
482
|
-
resolve(value: T): void;
|
|
483
|
-
reject(error: unknown): void;
|
|
484
|
-
};
|
|
485
|
-
|
|
486
|
-
type PendingPublication = {
|
|
487
|
-
entries: Array<{ memberId: string; result: ToolExecuteResult }>;
|
|
426
|
+
type PublicationEntry = OwnedMember & { result: ToolExecuteResult };
|
|
427
|
+
type Publication = {
|
|
428
|
+
entries: PublicationEntry[];
|
|
488
429
|
resolve(value: Map<string, ToolExecuteResult>): void;
|
|
489
430
|
reject(error: unknown): void;
|
|
431
|
+
remaining: Set<string>;
|
|
432
|
+
failure?: Extract<ToolCallReceiptCohortSettlement, { status: 'failed' }>;
|
|
490
433
|
};
|
|
491
434
|
|
|
492
|
-
type RequestedReceipt = {
|
|
493
|
-
memberId: string;
|
|
494
|
-
result: ToolExecuteResult;
|
|
495
|
-
} & OwnedGroup;
|
|
496
|
-
|
|
497
|
-
/** One queued publication and the receipts it is waiting to become durable. */
|
|
498
|
-
type PublicationClaim = {
|
|
499
|
-
publication: PendingPublication;
|
|
500
|
-
resultReceiptKeys: string[];
|
|
501
|
-
settled: boolean;
|
|
502
|
-
};
|
|
503
|
-
|
|
504
|
-
type ReceiptSettlement = { failed: boolean; error: unknown };
|
|
505
|
-
|
|
506
|
-
function groupMembers(
|
|
507
|
-
members: ToolCallReceiptCohortMember[],
|
|
508
|
-
): Map<string, Group> {
|
|
509
|
-
const groups = new Map<string, Group>();
|
|
510
|
-
const memberIds = new Set<string>();
|
|
511
|
-
for (const member of members) {
|
|
512
|
-
const memberId = required(member.memberId, 'member id');
|
|
513
|
-
if (memberIds.has(memberId)) {
|
|
514
|
-
throw new Error(`Duplicate Tool Call Receipt Cohort member ${memberId}.`);
|
|
515
|
-
}
|
|
516
|
-
memberIds.add(memberId);
|
|
517
|
-
const resultReceiptKey = required(
|
|
518
|
-
member.resultReceiptKey,
|
|
519
|
-
'result receipt key',
|
|
520
|
-
);
|
|
521
|
-
const group = groups.get(resultReceiptKey) ?? {
|
|
522
|
-
resultReceiptKey,
|
|
523
|
-
members: [],
|
|
524
|
-
force: false,
|
|
525
|
-
};
|
|
526
|
-
group.members.push({ ...member, memberId, resultReceiptKey });
|
|
527
|
-
group.force ||= member.force === true;
|
|
528
|
-
groups.set(resultReceiptKey, group);
|
|
529
|
-
}
|
|
530
|
-
return groups;
|
|
531
|
-
}
|
|
532
|
-
|
|
533
|
-
function deferredFor(
|
|
534
|
-
group: Group,
|
|
535
|
-
deferred: Map<string, Deferred<ToolExecuteResult>>,
|
|
536
|
-
): Deferred<ToolExecuteResult> {
|
|
537
|
-
let value = deferred.get(group.resultReceiptKey);
|
|
538
|
-
if (value) return value;
|
|
539
|
-
let resolve!: (result: ToolExecuteResult) => void;
|
|
540
|
-
let reject!: (error: unknown) => void;
|
|
541
|
-
const promise = new Promise<ToolExecuteResult>(
|
|
542
|
-
(resolvePromise, rejectPromise) => {
|
|
543
|
-
resolve = resolvePromise;
|
|
544
|
-
reject = rejectPromise;
|
|
545
|
-
},
|
|
546
|
-
);
|
|
547
|
-
void promise.catch(() => undefined);
|
|
548
|
-
value = { promise, resolve, reject };
|
|
549
|
-
deferred.set(group.resultReceiptKey, value);
|
|
550
|
-
return value;
|
|
551
|
-
}
|
|
552
|
-
|
|
553
|
-
function ownedEntries<T extends { memberId: string }>(
|
|
554
|
-
entries: T[],
|
|
555
|
-
states: Map<string, ToolCallReceiptCohortMemberState>,
|
|
556
|
-
owned: Map<string, OwnedGroup>,
|
|
557
|
-
): Array<T & OwnedGroup> {
|
|
558
|
-
const byKey = new Map<string, T & OwnedGroup>();
|
|
559
|
-
for (const entry of entries) {
|
|
560
|
-
const state = states.get(entry.memberId);
|
|
561
|
-
if (!state || state.kind !== 'owned') {
|
|
562
|
-
throw new ToolCallReceiptLeaseLostError(entry.memberId);
|
|
563
|
-
}
|
|
564
|
-
const groupOwned = owned.get(state.resultReceiptKey);
|
|
565
|
-
if (!groupOwned || !sameLease(groupOwned.lease, state.lease)) {
|
|
566
|
-
throw new ToolCallReceiptLeaseLostError(state.resultReceiptKey);
|
|
567
|
-
}
|
|
568
|
-
byKey.set(groupOwned.group.resultReceiptKey, { ...entry, ...groupOwned });
|
|
569
|
-
}
|
|
570
|
-
return [...byKey.values()];
|
|
571
|
-
}
|
|
572
|
-
|
|
573
|
-
function byKey(
|
|
574
|
-
records: ToolCallReceiptRecord[],
|
|
575
|
-
): Map<string, ToolCallReceiptRecord> {
|
|
576
|
-
return new Map(records.map((record) => [record.resultReceiptKey, record]));
|
|
577
|
-
}
|
|
578
|
-
|
|
579
435
|
function sameLease(
|
|
580
436
|
left: ToolCallReceiptLease,
|
|
581
437
|
right: ToolCallReceiptLease,
|
|
@@ -585,8 +441,7 @@ function sameLease(
|
|
|
585
441
|
|
|
586
442
|
function required(value: string, name: string): string {
|
|
587
443
|
const normalized = value?.trim();
|
|
588
|
-
if (!normalized)
|
|
444
|
+
if (!normalized)
|
|
589
445
|
throw new Error(`Tool Call Receipt Cohort requires a ${name}.`);
|
|
590
|
-
}
|
|
591
446
|
return normalized;
|
|
592
447
|
}
|