deepline 0.3.72 → 0.3.74
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/monitor-fleet-contract.ts +1 -1
- package/dist/bundling-sources/sdk/src/release.ts +1 -1
- package/dist/bundling-sources/shared_libs/monitors/monitor-fleet-limits.ts +5 -3
- package/dist/bundling-sources/shared_libs/play-runtime/context.ts +617 -623
- package/dist/bundling-sources/shared_libs/play-runtime/ctx-types.ts +10 -0
- package/dist/bundling-sources/shared_libs/play-runtime/durable-call-cache.ts +40 -10
- package/dist/bundling-sources/shared_libs/play-runtime/play-artifact-runtime-contract.ts +62 -0
- package/dist/bundling-sources/shared_libs/play-runtime/play-runtime-semantics.ts +122 -0
- package/dist/bundling-sources/shared_libs/play-runtime/tool-call/index.ts +2 -14
- package/dist/bundling-sources/shared_libs/play-runtime/tool-call/native-batch.ts +9 -92
- package/dist/bundling-sources/shared_libs/play-runtime/tool-call/receipt-cohort.ts +159 -621
- package/dist/bundling-sources/shared_libs/play-runtime/tool-call/runtime-step-receipts-adapter.ts +19 -13
- package/dist/bundling-sources/shared_libs/plays/contracts.ts +1 -4
- package/dist/bundling-sources/shared_libs/plays/tool-response-contract.ts +2 -4
- package/dist/cli/index.js +2 -2
- package/dist/cli/index.mjs +2 -2
- package/dist/index.d.mts +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.js +2 -2
- package/dist/index.mjs +2 -2
- package/dist/install-integrity.json +2 -0
- package/package.json +1 -1
|
@@ -1,34 +1,22 @@
|
|
|
1
|
+
import { COMPLETED_RECEIPT_CACHE_INSERT_LEASE_ID } from '../durable-receipt-execution';
|
|
1
2
|
import type { ToolExecuteResult } from '../tool-result';
|
|
2
|
-
import { createRuntimeReceiptHeartbeatSupervisor } from '../receipt-heartbeat-supervisor';
|
|
3
3
|
import {
|
|
4
4
|
ToolCallReceiptLeaseLostError,
|
|
5
5
|
type ToolCallReceiptLease,
|
|
6
|
-
type ToolCallReceiptRecoverySource,
|
|
7
6
|
type ToolResultReceipts,
|
|
8
7
|
} from './contract';
|
|
9
8
|
|
|
10
|
-
/**
|
|
11
|
-
* The durable state of one map-member Tool Result Receipt. This is deliberately
|
|
12
|
-
* smaller than RuntimeStepReceipt: row persistence and provider transport are
|
|
13
|
-
* not receipt-cohort concerns.
|
|
14
|
-
*/
|
|
15
9
|
export type ToolCallReceiptRecord = {
|
|
16
10
|
resultReceiptKey: string;
|
|
17
|
-
status: '
|
|
18
|
-
result
|
|
19
|
-
error?: unknown;
|
|
20
|
-
lease?: ToolCallReceiptLease;
|
|
21
|
-
leaseExpiresAt?: string | null;
|
|
22
|
-
claimState?: 'claimed' | 'existing';
|
|
11
|
+
status: 'completed' | 'skipped';
|
|
12
|
+
result: ToolExecuteResult;
|
|
23
13
|
};
|
|
24
14
|
|
|
25
15
|
export type ToolCallReceiptCohortMember = {
|
|
26
|
-
/** A caller-local identity; it is never persisted. */
|
|
27
16
|
memberId: string;
|
|
28
17
|
resultReceiptKey: string;
|
|
29
18
|
force?: boolean;
|
|
30
19
|
forceFailedRefresh?: boolean;
|
|
31
|
-
/** Number of poll reads before a foreign/incomplete receipt is reclaimed. */
|
|
32
20
|
waitMaxAttempts?: number;
|
|
33
21
|
};
|
|
34
22
|
|
|
@@ -36,26 +24,6 @@ export type ToolCallReceiptCohortStore = {
|
|
|
36
24
|
read(input: {
|
|
37
25
|
resultReceiptKeys: string[];
|
|
38
26
|
}): Promise<ToolCallReceiptRecord[]>;
|
|
39
|
-
/**
|
|
40
|
-
* The Adapter may partition this vector by refresh policy to reach a legacy
|
|
41
|
-
* gateway that accepts one policy per RPC. Results are key-aligned by value,
|
|
42
|
-
* not array position, so duplicates are forbidden at this seam.
|
|
43
|
-
*/
|
|
44
|
-
claim(input: {
|
|
45
|
-
ownerRunId: string;
|
|
46
|
-
ownerAttempt: number;
|
|
47
|
-
receipts: Array<{
|
|
48
|
-
resultReceiptKey: string;
|
|
49
|
-
force: boolean;
|
|
50
|
-
forceFailedRefresh: boolean;
|
|
51
|
-
reclaimRunning: boolean;
|
|
52
|
-
}>;
|
|
53
|
-
}): Promise<ToolCallReceiptRecord[]>;
|
|
54
|
-
heartbeat(input: {
|
|
55
|
-
ownerRunId: string;
|
|
56
|
-
ownerAttempt: number;
|
|
57
|
-
receipts: Array<{ resultReceiptKey: string; lease: ToolCallReceiptLease }>;
|
|
58
|
-
}): Promise<ToolCallReceiptRecord[]>;
|
|
59
27
|
complete(input: {
|
|
60
28
|
ownerRunId: string;
|
|
61
29
|
ownerAttempt: number;
|
|
@@ -65,24 +33,14 @@ export type ToolCallReceiptCohortStore = {
|
|
|
65
33
|
result: ToolExecuteResult;
|
|
66
34
|
}>;
|
|
67
35
|
}): Promise<ToolCallReceiptRecord[]>;
|
|
68
|
-
fail(input: {
|
|
69
|
-
ownerRunId: string;
|
|
70
|
-
ownerAttempt: number;
|
|
71
|
-
receipts: Array<{
|
|
72
|
-
resultReceiptKey: string;
|
|
73
|
-
lease: ToolCallReceiptLease;
|
|
74
|
-
error: unknown;
|
|
75
|
-
}>;
|
|
76
|
-
}): Promise<ToolCallReceiptRecord[]>;
|
|
77
36
|
};
|
|
78
37
|
|
|
79
38
|
export type ToolCallReceiptCohortMemberState =
|
|
80
39
|
| {
|
|
81
40
|
kind: 'completed';
|
|
82
41
|
result: ToolExecuteResult;
|
|
83
|
-
source: 'cache' | '
|
|
42
|
+
source: 'cache' | 'owner';
|
|
84
43
|
}
|
|
85
|
-
| { kind: 'failed'; error: unknown }
|
|
86
44
|
| {
|
|
87
45
|
kind: 'owned';
|
|
88
46
|
resultReceiptKey: string;
|
|
@@ -95,34 +53,20 @@ export type ToolCallReceiptCohortMemberState =
|
|
|
95
53
|
resultReceiptKey: string;
|
|
96
54
|
ownerMemberId: string;
|
|
97
55
|
result: Promise<ToolExecuteResult>;
|
|
98
|
-
}
|
|
99
|
-
| {
|
|
100
|
-
/** A foreign receipt is being hydrated/reclaimed by the cohort. */
|
|
101
|
-
kind: 'recovering';
|
|
102
|
-
resultReceiptKey: string;
|
|
103
|
-
result: Promise<ToolExecuteResult>;
|
|
104
56
|
};
|
|
105
57
|
|
|
106
58
|
export type ToolCallReceiptCohort = {
|
|
107
59
|
member(memberId: string): ToolCallReceiptCohortMemberState;
|
|
108
|
-
/** A native batch uses this after splitting one physical provider response. */
|
|
109
60
|
complete(
|
|
110
61
|
input: Array<{ memberId: string; result: ToolExecuteResult }>,
|
|
111
62
|
): Promise<Map<string, ToolExecuteResult>>;
|
|
112
63
|
fail(input: Array<{ memberId: string; error: unknown }>): Promise<void>;
|
|
113
|
-
/** One RPC for every distinct owned receipt, including queued map tail work. */
|
|
114
64
|
heartbeat(): Promise<'active' | 'terminal'>;
|
|
115
|
-
/**
|
|
116
|
-
* Starts the cohort's ownership supervisor. Per-member receipt views only
|
|
117
|
-
* assert this shared fence; they never fragment it into one heartbeat RPC per
|
|
118
|
-
* provider call.
|
|
119
|
-
*/
|
|
120
65
|
startHeartbeat(input: {
|
|
121
66
|
intervalMs: number;
|
|
122
67
|
onLeaseLost(error: ToolCallReceiptLeaseLostError): void;
|
|
123
68
|
onTransientFailure?(error: unknown): void;
|
|
124
69
|
}): { stop(): void; assertOwned(): void };
|
|
125
|
-
/** Wait for foreign-receipt hydration/reclaim work started by eager acquire. */
|
|
126
70
|
settle(): Promise<void>;
|
|
127
71
|
};
|
|
128
72
|
|
|
@@ -130,41 +74,21 @@ export type ToolCallReceiptCohortJob = {
|
|
|
130
74
|
acquire(input: {
|
|
131
75
|
owner: { runId: string; attempt: number };
|
|
132
76
|
members: ToolCallReceiptCohortMember[];
|
|
133
|
-
/**
|
|
134
|
-
* Return after the atomic initial claim so independently owned map rows
|
|
135
|
-
* can start provider work while foreign rows hydrate. The default keeps
|
|
136
|
-
* the standalone Module's all-settled acquisition contract.
|
|
137
|
-
*/
|
|
138
77
|
eager?: boolean;
|
|
139
78
|
}): Promise<ToolCallReceiptCohort>;
|
|
140
79
|
};
|
|
141
80
|
|
|
142
|
-
export class ToolCallReceiptCohortWaitTimeoutError extends Error {
|
|
143
|
-
constructor(resultReceiptKey: string) {
|
|
144
|
-
super(`Timed out waiting for Tool Result Receipt ${resultReceiptKey}.`);
|
|
145
|
-
this.name = 'ToolCallReceiptCohortWaitTimeoutError';
|
|
146
|
-
}
|
|
147
|
-
}
|
|
148
|
-
|
|
149
81
|
/**
|
|
150
|
-
*
|
|
151
|
-
*
|
|
152
|
-
*
|
|
153
|
-
*
|
|
154
|
-
* one heartbeat for undispatched work, and a terminal fanout after a native
|
|
155
|
-
* batch splits. Keeping that protocol out of PlayContext makes ToolCall's
|
|
156
|
-
* scalar Interface remain small without discarding the real map behaviour.
|
|
82
|
+
* A map Tool Result Receipt cohort is a completed-result cache over exact
|
|
83
|
+
* keys. It does not claim work, wait for other runs, send heartbeats, or write
|
|
84
|
+
* errors. Cache misses execute independently and successful writes converge
|
|
85
|
+
* through the store's atomic completed-result insert.
|
|
157
86
|
*/
|
|
158
87
|
export function createToolCallReceiptCohortJob(input: {
|
|
159
88
|
store: ToolCallReceiptCohortStore;
|
|
160
|
-
sleep?: (milliseconds: number) => Promise<void>;
|
|
161
|
-
waitDelayMs?: number;
|
|
162
89
|
}): ToolCallReceiptCohortJob {
|
|
163
|
-
const sleep = input.sleep ?? defaultSleep;
|
|
164
|
-
const waitDelayMs = positive(input.waitDelayMs ?? 250, 'wait delay');
|
|
165
|
-
|
|
166
90
|
return {
|
|
167
|
-
async acquire({ owner, members
|
|
91
|
+
async acquire({ owner, members }) {
|
|
168
92
|
const ownerRunId = required(owner.runId, 'owner run id');
|
|
169
93
|
if (!Number.isInteger(owner.attempt) || owner.attempt < 0) {
|
|
170
94
|
throw new Error(
|
|
@@ -178,414 +102,137 @@ export function createToolCallReceiptCohortJob(input: {
|
|
|
178
102
|
}
|
|
179
103
|
|
|
180
104
|
const groups = groupMembers(members);
|
|
105
|
+
const records = byKey(
|
|
106
|
+
await input.store.read({
|
|
107
|
+
resultReceiptKeys: [...groups.values()].map(
|
|
108
|
+
(group) => group.resultReceiptKey,
|
|
109
|
+
),
|
|
110
|
+
}),
|
|
111
|
+
);
|
|
181
112
|
const states = new Map<string, ToolCallReceiptCohortMemberState>();
|
|
182
|
-
const
|
|
113
|
+
const deferred = new Map<string, Deferred<ToolExecuteResult>>();
|
|
183
114
|
const owned = new Map<string, OwnedGroup>();
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
memberId: string;
|
|
187
|
-
result: ToolExecuteResult;
|
|
188
|
-
resolve(result: ToolExecuteResult): void;
|
|
189
|
-
reject(error: unknown): void;
|
|
190
|
-
}> = [];
|
|
191
|
-
const queuedFailures: Array<{
|
|
192
|
-
memberId: string;
|
|
193
|
-
error: unknown;
|
|
194
|
-
resolve(): void;
|
|
195
|
-
reject(error: unknown): void;
|
|
196
|
-
}> = [];
|
|
197
|
-
let completionFlushScheduled = false;
|
|
198
|
-
let failureFlushScheduled = false;
|
|
115
|
+
const pendingPublications: PendingPublication[] = [];
|
|
116
|
+
let publicationFlushScheduled = false;
|
|
199
117
|
|
|
200
|
-
const resolveGroup = (
|
|
201
|
-
group
|
|
202
|
-
|
|
203
|
-
source: 'cache' | 'in_flight' | 'owner',
|
|
204
|
-
) => {
|
|
205
|
-
const deferred = deferredFor(group, resultDeferred);
|
|
206
|
-
deferred.resolve(result);
|
|
207
|
-
for (const [index, member] of group.members.entries()) {
|
|
118
|
+
const resolveGroup = (group: Group, result: ToolExecuteResult) => {
|
|
119
|
+
deferredFor(group, deferred).resolve(result);
|
|
120
|
+
for (const member of group.members) {
|
|
208
121
|
states.set(member.memberId, {
|
|
209
122
|
kind: 'completed',
|
|
210
123
|
result,
|
|
211
|
-
|
|
212
|
-
// map cells attached to that receipt are followers even though
|
|
213
|
-
// the completion fanout is local to this process.
|
|
214
|
-
source: source === 'owner' && index > 0 ? 'in_flight' : source,
|
|
124
|
+
source: 'owner',
|
|
215
125
|
});
|
|
216
126
|
}
|
|
217
127
|
};
|
|
218
128
|
const rejectGroup = (group: Group, error: unknown) => {
|
|
219
|
-
|
|
220
|
-
deferred.reject(error);
|
|
221
|
-
for (const member of group.members) {
|
|
222
|
-
states.set(member.memberId, { kind: 'failed', error });
|
|
223
|
-
}
|
|
129
|
+
deferredFor(group, deferred).reject(error);
|
|
224
130
|
};
|
|
225
131
|
|
|
226
|
-
const
|
|
227
|
-
const
|
|
228
|
-
if (!
|
|
229
|
-
|
|
132
|
+
for (const group of groups.values()) {
|
|
133
|
+
const cached = records.get(group.resultReceiptKey);
|
|
134
|
+
if (!group.force && cached) {
|
|
135
|
+
for (const member of group.members) {
|
|
136
|
+
states.set(member.memberId, {
|
|
137
|
+
kind: 'completed',
|
|
138
|
+
result: cached.result,
|
|
139
|
+
source: 'cache',
|
|
140
|
+
});
|
|
141
|
+
}
|
|
142
|
+
continue;
|
|
230
143
|
}
|
|
231
|
-
const
|
|
232
|
-
|
|
144
|
+
const lease = {
|
|
145
|
+
leaseId: COMPLETED_RECEIPT_CACHE_INSERT_LEASE_ID,
|
|
146
|
+
ownerRunId,
|
|
147
|
+
};
|
|
148
|
+
const groupOwned = { group, lease };
|
|
233
149
|
owned.set(group.resultReceiptKey, groupOwned);
|
|
150
|
+
const ownerMember = group.members[0]!;
|
|
234
151
|
states.set(ownerMember.memberId, {
|
|
235
152
|
kind: 'owned',
|
|
236
153
|
resultReceiptKey: group.resultReceiptKey,
|
|
237
154
|
lease,
|
|
238
|
-
...(record.leaseExpiresAt
|
|
239
|
-
? { leaseExpiresAt: record.leaseExpiresAt }
|
|
240
|
-
: {}),
|
|
241
155
|
receipts: receiptView(ownerMember.memberId, groupOwned),
|
|
242
156
|
});
|
|
243
|
-
const deferred = deferredFor(group, resultDeferred);
|
|
244
157
|
for (const follower of group.members.slice(1)) {
|
|
245
158
|
states.set(follower.memberId, {
|
|
246
159
|
kind: 'following',
|
|
247
160
|
resultReceiptKey: group.resultReceiptKey,
|
|
248
161
|
ownerMemberId: ownerMember.memberId,
|
|
249
|
-
result: deferred.promise,
|
|
162
|
+
result: deferredFor(group, deferred).promise,
|
|
250
163
|
});
|
|
251
164
|
}
|
|
252
|
-
};
|
|
253
|
-
|
|
254
|
-
const applyClaim = (
|
|
255
|
-
group: Group,
|
|
256
|
-
record: ToolCallReceiptRecord | undefined,
|
|
257
|
-
source: 'cache' | 'in_flight',
|
|
258
|
-
) => {
|
|
259
|
-
if (!record) return false;
|
|
260
|
-
if (isCompleted(record)) {
|
|
261
|
-
resolveGroup(group, record.result, source);
|
|
262
|
-
return true;
|
|
263
|
-
}
|
|
264
|
-
if (record.status === 'failed') {
|
|
265
|
-
rejectGroup(
|
|
266
|
-
group,
|
|
267
|
-
record.error ??
|
|
268
|
-
new Error(
|
|
269
|
-
`Tool Result Receipt ${group.resultReceiptKey} failed.`,
|
|
270
|
-
),
|
|
271
|
-
);
|
|
272
|
-
return true;
|
|
273
|
-
}
|
|
274
|
-
if (isOwned(record, ownerRunId)) {
|
|
275
|
-
ownGroup(group, record);
|
|
276
|
-
return true;
|
|
277
|
-
}
|
|
278
|
-
return false;
|
|
279
|
-
};
|
|
280
|
-
|
|
281
|
-
const claimGroups = async (targets: Group[], reclaimRunning: boolean) => {
|
|
282
|
-
if (targets.length === 0)
|
|
283
|
-
return new Map<string, ToolCallReceiptRecord>();
|
|
284
|
-
const response = await input.store.claim({
|
|
285
|
-
ownerRunId,
|
|
286
|
-
ownerAttempt: owner.attempt,
|
|
287
|
-
receipts: targets.map((group) => ({
|
|
288
|
-
resultReceiptKey: group.resultReceiptKey,
|
|
289
|
-
force: group.force,
|
|
290
|
-
forceFailedRefresh: group.forceFailedRefresh,
|
|
291
|
-
reclaimRunning,
|
|
292
|
-
})),
|
|
293
|
-
});
|
|
294
|
-
return byKey(response);
|
|
295
|
-
};
|
|
296
|
-
|
|
297
|
-
// A forced refresh must not overlap an active foreign lease. Other
|
|
298
|
-
// requests go straight to the atomic bulk claim, matching the existing
|
|
299
|
-
// gateway protocol.
|
|
300
|
-
const forceGroups = [...groups.values()].filter((group) => group.force);
|
|
301
|
-
const forceExisting = forceGroups.length
|
|
302
|
-
? byKey(
|
|
303
|
-
await input.store.read({
|
|
304
|
-
resultReceiptKeys: forceGroups.map(
|
|
305
|
-
(group) => group.resultReceiptKey,
|
|
306
|
-
),
|
|
307
|
-
}),
|
|
308
|
-
)
|
|
309
|
-
: new Map<string, ToolCallReceiptRecord>();
|
|
310
|
-
const waitGroups: Group[] = [];
|
|
311
|
-
const initialClaimGroups: Group[] = [];
|
|
312
|
-
for (const group of groups.values()) {
|
|
313
|
-
const existing = forceExisting.get(group.resultReceiptKey);
|
|
314
|
-
if (group.force && existing && isLiveLeased(existing)) {
|
|
315
|
-
waitGroups.push(group);
|
|
316
|
-
} else {
|
|
317
|
-
initialClaimGroups.push(group);
|
|
318
|
-
}
|
|
319
|
-
}
|
|
320
|
-
const claims = await claimGroups(initialClaimGroups, false);
|
|
321
|
-
for (const group of initialClaimGroups) {
|
|
322
|
-
if (!applyClaim(group, claims.get(group.resultReceiptKey), 'cache')) {
|
|
323
|
-
waitGroups.push(group);
|
|
324
|
-
}
|
|
325
165
|
}
|
|
326
166
|
|
|
327
|
-
const
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
1,
|
|
332
|
-
...group.members.map((member) => member.waitMaxAttempts ?? 240),
|
|
333
|
-
),
|
|
334
|
-
]),
|
|
335
|
-
);
|
|
336
|
-
for (const group of waitGroups) {
|
|
337
|
-
states.set(group.members[0]!.memberId, {
|
|
338
|
-
kind: 'recovering',
|
|
339
|
-
resultReceiptKey: group.resultReceiptKey,
|
|
340
|
-
result: deferredFor(group, resultDeferred).promise,
|
|
341
|
-
});
|
|
342
|
-
for (const member of group.members.slice(1)) {
|
|
343
|
-
states.set(member.memberId, {
|
|
344
|
-
kind: 'recovering',
|
|
345
|
-
resultReceiptKey: group.resultReceiptKey,
|
|
346
|
-
result: deferredFor(group, resultDeferred).promise,
|
|
347
|
-
});
|
|
348
|
-
}
|
|
349
|
-
}
|
|
350
|
-
|
|
351
|
-
const settleWaitGroups = async (): Promise<void> => {
|
|
352
|
-
const pending = new Map(
|
|
353
|
-
waitGroups.map((group) => [group.resultReceiptKey, group]),
|
|
354
|
-
);
|
|
167
|
+
const flushPublications = async (): Promise<void> => {
|
|
168
|
+
publicationFlushScheduled = false;
|
|
169
|
+
const pending = pendingPublications.splice(0);
|
|
170
|
+
if (pending.length === 0) return;
|
|
355
171
|
try {
|
|
356
|
-
const
|
|
357
|
-
|
|
358
|
-
|
|
359
|
-
|
|
360
|
-
|
|
361
|
-
|
|
362
|
-
|
|
363
|
-
|
|
364
|
-
|
|
365
|
-
|
|
366
|
-
|
|
367
|
-
|
|
368
|
-
|
|
369
|
-
|
|
370
|
-
|
|
371
|
-
|
|
372
|
-
|
|
373
|
-
|
|
374
|
-
|
|
375
|
-
|
|
376
|
-
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
// Waiting until the final attempt strands the map resolver for
|
|
385
|
-
// the entire bounded-recovery budget after a perfectly healthy
|
|
386
|
-
// first call completes.
|
|
387
|
-
if (group.force) {
|
|
388
|
-
if (!record || !isLiveLeased(record)) {
|
|
389
|
-
const claimed = await claimGroups([group], true);
|
|
390
|
-
if (
|
|
391
|
-
applyClaim(
|
|
392
|
-
group,
|
|
393
|
-
claimed.get(group.resultReceiptKey),
|
|
394
|
-
'cache',
|
|
395
|
-
)
|
|
396
|
-
) {
|
|
397
|
-
pending.delete(group.resultReceiptKey);
|
|
398
|
-
}
|
|
399
|
-
}
|
|
400
|
-
continue;
|
|
401
|
-
}
|
|
402
|
-
if (!record) continue;
|
|
403
|
-
if (isCompleted(record)) {
|
|
404
|
-
resolveGroup(group, record.result, 'in_flight');
|
|
405
|
-
pending.delete(group.resultReceiptKey);
|
|
406
|
-
} else if (record.status === 'failed') {
|
|
407
|
-
rejectGroup(
|
|
408
|
-
group,
|
|
409
|
-
record.error ??
|
|
410
|
-
new Error(
|
|
411
|
-
`Tool Result Receipt ${group.resultReceiptKey} failed.`,
|
|
412
|
-
),
|
|
413
|
-
);
|
|
414
|
-
pending.delete(group.resultReceiptKey);
|
|
415
|
-
}
|
|
172
|
+
const requested = ownedEntries(
|
|
173
|
+
pending.flatMap((publication) => publication.entries),
|
|
174
|
+
states,
|
|
175
|
+
owned,
|
|
176
|
+
);
|
|
177
|
+
const persisted = byKey(
|
|
178
|
+
await input.store.complete({
|
|
179
|
+
ownerRunId,
|
|
180
|
+
ownerAttempt: owner.attempt,
|
|
181
|
+
receipts: requested.map(({ group, lease, result }) => ({
|
|
182
|
+
resultReceiptKey: group.resultReceiptKey,
|
|
183
|
+
lease,
|
|
184
|
+
result,
|
|
185
|
+
})),
|
|
186
|
+
}),
|
|
187
|
+
);
|
|
188
|
+
const results = new Map<string, ToolExecuteResult>();
|
|
189
|
+
for (const entry of requested) {
|
|
190
|
+
// A completed-result insert is immutable: another independent
|
|
191
|
+
// cold miss may have published first. That does not retroactively
|
|
192
|
+
// turn this caller into a cache follower. It already executed, so
|
|
193
|
+
// return its own result and use the persisted entry only for later
|
|
194
|
+
// cache reads.
|
|
195
|
+
const result = entry.result;
|
|
196
|
+
owned.delete(entry.group.resultReceiptKey);
|
|
197
|
+
resolveGroup(entry.group, result);
|
|
198
|
+
for (const member of entry.group.members) {
|
|
199
|
+
results.set(member.memberId, result);
|
|
416
200
|
}
|
|
417
201
|
}
|
|
418
|
-
|
|
419
|
-
|
|
420
|
-
|
|
421
|
-
|
|
422
|
-
|
|
423
|
-
|
|
424
|
-
|
|
425
|
-
|
|
426
|
-
continue;
|
|
427
|
-
}
|
|
428
|
-
const error = new ToolCallReceiptCohortWaitTimeoutError(
|
|
429
|
-
group.resultReceiptKey,
|
|
202
|
+
for (const publication of pending) {
|
|
203
|
+
publication.resolve(
|
|
204
|
+
new Map(
|
|
205
|
+
publication.entries.flatMap((entry) => {
|
|
206
|
+
const result = results.get(entry.memberId);
|
|
207
|
+
return result ? [[entry.memberId, result] as const] : [];
|
|
208
|
+
}),
|
|
209
|
+
),
|
|
430
210
|
);
|
|
431
|
-
rejectGroup(group, error);
|
|
432
|
-
throw error;
|
|
433
211
|
}
|
|
434
212
|
} catch (error) {
|
|
435
|
-
for (const
|
|
436
|
-
throw error;
|
|
437
|
-
}
|
|
438
|
-
};
|
|
439
|
-
const settlement = settleWaitGroups();
|
|
440
|
-
// An eager caller deliberately awaits this after independently owned
|
|
441
|
-
// work settles. Keep a rejection observed meanwhile so a prompt adapter
|
|
442
|
-
// read failure cannot surface as an unhandled rejection.
|
|
443
|
-
void settlement.catch(() => undefined);
|
|
444
|
-
if (!eager) await settlement;
|
|
445
|
-
|
|
446
|
-
const assertOwned = () => {
|
|
447
|
-
if (leaseLost) throw leaseLost;
|
|
448
|
-
for (const group of owned.values()) {
|
|
449
|
-
const state = states.get(group.group.members[0]!.memberId);
|
|
450
|
-
if (
|
|
451
|
-
!state ||
|
|
452
|
-
state.kind !== 'owned' ||
|
|
453
|
-
state.lease.leaseId !== group.lease.leaseId
|
|
454
|
-
) {
|
|
455
|
-
throw new ToolCallReceiptLeaseLostError(
|
|
456
|
-
group.group.resultReceiptKey,
|
|
457
|
-
);
|
|
458
|
-
}
|
|
213
|
+
for (const publication of pending) publication.reject(error);
|
|
459
214
|
}
|
|
460
215
|
};
|
|
461
216
|
|
|
462
|
-
const
|
|
463
|
-
if (owned.size === 0) return 'terminal';
|
|
464
|
-
const current = [...owned.values()];
|
|
465
|
-
const response = byKey(
|
|
466
|
-
await input.store.heartbeat({
|
|
467
|
-
ownerRunId,
|
|
468
|
-
ownerAttempt: owner.attempt,
|
|
469
|
-
receipts: current.map(({ group, lease }) => ({
|
|
470
|
-
resultReceiptKey: group.resultReceiptKey,
|
|
471
|
-
lease,
|
|
472
|
-
})),
|
|
473
|
-
}),
|
|
474
|
-
);
|
|
475
|
-
let active = false;
|
|
476
|
-
for (const groupOwned of current) {
|
|
477
|
-
const record = response.get(groupOwned.group.resultReceiptKey);
|
|
478
|
-
if (record && isCompleted(record)) {
|
|
479
|
-
owned.delete(groupOwned.group.resultReceiptKey);
|
|
480
|
-
resolveGroup(groupOwned.group, record.result, 'in_flight');
|
|
481
|
-
continue;
|
|
482
|
-
}
|
|
483
|
-
if (record?.status === 'failed') {
|
|
484
|
-
owned.delete(groupOwned.group.resultReceiptKey);
|
|
485
|
-
rejectGroup(
|
|
486
|
-
groupOwned.group,
|
|
487
|
-
record.error ??
|
|
488
|
-
new Error(
|
|
489
|
-
`Tool Result Receipt ${groupOwned.group.resultReceiptKey} failed.`,
|
|
490
|
-
),
|
|
491
|
-
);
|
|
492
|
-
continue;
|
|
493
|
-
}
|
|
494
|
-
if (!record || !isSameLease(record, groupOwned.lease)) {
|
|
495
|
-
leaseLost ??= new ToolCallReceiptLeaseLostError(
|
|
496
|
-
groupOwned.group.resultReceiptKey,
|
|
497
|
-
);
|
|
498
|
-
throw leaseLost;
|
|
499
|
-
}
|
|
500
|
-
active = true;
|
|
501
|
-
}
|
|
502
|
-
return active ? 'active' : 'terminal';
|
|
503
|
-
};
|
|
504
|
-
|
|
505
|
-
const complete = async (
|
|
217
|
+
const publish = async (
|
|
506
218
|
entries: Array<{ memberId: string; result: ToolExecuteResult }>,
|
|
507
|
-
) => {
|
|
508
|
-
|
|
509
|
-
|
|
510
|
-
|
|
511
|
-
|
|
512
|
-
|
|
513
|
-
ownerAttempt: owner.attempt,
|
|
514
|
-
receipts: requested.map(({ group, lease, result }) => ({
|
|
515
|
-
resultReceiptKey: group.resultReceiptKey,
|
|
516
|
-
lease,
|
|
517
|
-
result,
|
|
518
|
-
})),
|
|
519
|
-
}),
|
|
520
|
-
);
|
|
521
|
-
const results = new Map<string, ToolExecuteResult>();
|
|
522
|
-
for (const entry of requested) {
|
|
523
|
-
const persisted = response.get(entry.group.resultReceiptKey);
|
|
524
|
-
// Completion gateways are allowed to return a compact terminal
|
|
525
|
-
// acknowledgement after durably storing the result. That response
|
|
526
|
-
// is represented as a `no_result` wrapper by the adapter; it is not
|
|
527
|
-
// the provider result and must not replace this invocation's live
|
|
528
|
-
// value. A real no-result provider response is identical to the
|
|
529
|
-
// submitted entry, so retaining the entry is correct there too.
|
|
530
|
-
const result =
|
|
531
|
-
isCompleted(persisted) && persisted.result.status !== 'no_result'
|
|
532
|
-
? persisted.result
|
|
533
|
-
: entry.result;
|
|
534
|
-
owned.delete(entry.group.resultReceiptKey);
|
|
535
|
-
resolveGroup(entry.group, result, 'owner');
|
|
536
|
-
for (const member of entry.group.members)
|
|
537
|
-
results.set(member.memberId, result);
|
|
538
|
-
}
|
|
539
|
-
return results;
|
|
540
|
-
};
|
|
541
|
-
|
|
542
|
-
const fail = async (
|
|
543
|
-
entries: Array<{ memberId: string; error: unknown }>,
|
|
544
|
-
) => {
|
|
545
|
-
const requested = ownedEntries(entries, states, owned);
|
|
546
|
-
if (requested.length === 0) return;
|
|
547
|
-
await input.store.fail({
|
|
548
|
-
ownerRunId,
|
|
549
|
-
ownerAttempt: owner.attempt,
|
|
550
|
-
receipts: requested.map(({ group, lease, error }) => ({
|
|
551
|
-
resultReceiptKey: group.resultReceiptKey,
|
|
552
|
-
lease,
|
|
553
|
-
error,
|
|
554
|
-
})),
|
|
555
|
-
});
|
|
556
|
-
for (const entry of requested) {
|
|
557
|
-
owned.delete(entry.group.resultReceiptKey);
|
|
558
|
-
rejectGroup(entry.group, entry.error);
|
|
559
|
-
}
|
|
560
|
-
};
|
|
561
|
-
|
|
562
|
-
const flushCompletions = async (): Promise<void> => {
|
|
563
|
-
completionFlushScheduled = false;
|
|
564
|
-
const entries = queuedCompletions.splice(0);
|
|
565
|
-
if (entries.length === 0) return;
|
|
566
|
-
try {
|
|
567
|
-
const results = await complete(
|
|
568
|
-
entries.map(({ memberId, result }) => ({ memberId, result })),
|
|
569
|
-
);
|
|
570
|
-
for (const entry of entries) {
|
|
571
|
-
entry.resolve(results.get(entry.memberId) ?? entry.result);
|
|
572
|
-
}
|
|
573
|
-
} catch (error) {
|
|
574
|
-
for (const entry of entries) entry.reject(error);
|
|
575
|
-
}
|
|
576
|
-
};
|
|
577
|
-
const flushFailures = async (): Promise<void> => {
|
|
578
|
-
failureFlushScheduled = false;
|
|
579
|
-
const entries = queuedFailures.splice(0);
|
|
580
|
-
if (entries.length === 0) return;
|
|
581
|
-
try {
|
|
582
|
-
await fail(
|
|
583
|
-
entries.map(({ memberId, error }) => ({ memberId, error })),
|
|
584
|
-
);
|
|
585
|
-
for (const entry of entries) entry.resolve();
|
|
586
|
-
} catch (error) {
|
|
587
|
-
for (const entry of entries) entry.reject(error);
|
|
219
|
+
): Promise<Map<string, ToolExecuteResult>> => {
|
|
220
|
+
// Validate ownership before queuing, then coalesce writes that settle
|
|
221
|
+
// in this event-loop turn. This batches cache persistence only; it
|
|
222
|
+
// never waits for or coordinates another run's provider call.
|
|
223
|
+
if (ownedEntries(entries, states, owned).length === 0) {
|
|
224
|
+
return new Map();
|
|
588
225
|
}
|
|
226
|
+
return await new Promise<Map<string, ToolExecuteResult>>(
|
|
227
|
+
(resolve, reject) => {
|
|
228
|
+
pendingPublications.push({ entries, resolve, reject });
|
|
229
|
+
if (publicationFlushScheduled) return;
|
|
230
|
+
publicationFlushScheduled = true;
|
|
231
|
+
setImmediate(() => {
|
|
232
|
+
void flushPublications();
|
|
233
|
+
});
|
|
234
|
+
},
|
|
235
|
+
);
|
|
589
236
|
};
|
|
590
237
|
|
|
591
238
|
function receiptView(
|
|
@@ -593,6 +240,17 @@ export function createToolCallReceiptCohortJob(input: {
|
|
|
593
240
|
groupOwned: OwnedGroup,
|
|
594
241
|
): ToolResultReceipts {
|
|
595
242
|
const { group, lease } = groupOwned;
|
|
243
|
+
const assert = (candidate: {
|
|
244
|
+
resultReceiptKey: string;
|
|
245
|
+
lease: ToolCallReceiptLease;
|
|
246
|
+
}) => {
|
|
247
|
+
if (
|
|
248
|
+
candidate.resultReceiptKey !== group.resultReceiptKey ||
|
|
249
|
+
!sameLease(candidate.lease, lease)
|
|
250
|
+
) {
|
|
251
|
+
throw new ToolCallReceiptLeaseLostError(candidate.resultReceiptKey);
|
|
252
|
+
}
|
|
253
|
+
};
|
|
596
254
|
return {
|
|
597
255
|
async claim(candidate) {
|
|
598
256
|
if (
|
|
@@ -614,86 +272,26 @@ export function createToolCallReceiptCohortJob(input: {
|
|
|
614
272
|
candidate.resultReceiptKey,
|
|
615
273
|
);
|
|
616
274
|
}
|
|
617
|
-
return await deferredFor(group,
|
|
275
|
+
return await deferredFor(group, deferred).promise;
|
|
618
276
|
},
|
|
619
277
|
async heartbeat(candidate) {
|
|
620
|
-
|
|
621
|
-
|
|
622
|
-
!sameLease(candidate.lease, lease)
|
|
623
|
-
)
|
|
624
|
-
return 'lost';
|
|
625
|
-
try {
|
|
626
|
-
assertOwned();
|
|
627
|
-
// Preserve the per-provider-admission fence from the old map
|
|
628
|
-
// path. The cohort-wide heartbeat keeps queued work alive;
|
|
629
|
-
// this one-member check closes the exact dispatch window.
|
|
630
|
-
const record = byKey(
|
|
631
|
-
await input.store.heartbeat({
|
|
632
|
-
ownerRunId,
|
|
633
|
-
ownerAttempt: owner.attempt,
|
|
634
|
-
receipts: [
|
|
635
|
-
{ resultReceiptKey: group.resultReceiptKey, lease },
|
|
636
|
-
],
|
|
637
|
-
}),
|
|
638
|
-
).get(group.resultReceiptKey);
|
|
639
|
-
if (record && isCompleted(record)) {
|
|
640
|
-
owned.delete(group.resultReceiptKey);
|
|
641
|
-
resolveGroup(group, record.result, 'in_flight');
|
|
642
|
-
return 'completed';
|
|
643
|
-
}
|
|
644
|
-
if (!record || !isSameLease(record, lease)) return 'lost';
|
|
645
|
-
return 'active';
|
|
646
|
-
} catch (error) {
|
|
647
|
-
if (error instanceof ToolCallReceiptLeaseLostError) return 'lost';
|
|
648
|
-
throw error;
|
|
649
|
-
}
|
|
278
|
+
assert(candidate);
|
|
279
|
+
return owned.has(group.resultReceiptKey) ? 'active' : 'completed';
|
|
650
280
|
},
|
|
651
281
|
async complete(candidate) {
|
|
652
|
-
|
|
653
|
-
|
|
654
|
-
|
|
655
|
-
)
|
|
656
|
-
|
|
657
|
-
|
|
658
|
-
)
|
|
659
|
-
}
|
|
660
|
-
const result = await new Promise<ToolExecuteResult>(
|
|
661
|
-
(resolve, reject) => {
|
|
662
|
-
queuedCompletions.push({
|
|
663
|
-
memberId,
|
|
664
|
-
result: candidate.result,
|
|
665
|
-
resolve,
|
|
666
|
-
reject,
|
|
667
|
-
});
|
|
668
|
-
if (!completionFlushScheduled) {
|
|
669
|
-
completionFlushScheduled = true;
|
|
670
|
-
setTimeout(() => void flushCompletions(), 0);
|
|
671
|
-
}
|
|
672
|
-
},
|
|
673
|
-
);
|
|
674
|
-
return { kind: 'stored', result };
|
|
282
|
+
assert(candidate);
|
|
283
|
+
const results = await publish([
|
|
284
|
+
{ memberId, result: candidate.result },
|
|
285
|
+
]);
|
|
286
|
+
return {
|
|
287
|
+
kind: 'stored',
|
|
288
|
+
result: results.get(memberId) ?? candidate.result,
|
|
289
|
+
};
|
|
675
290
|
},
|
|
676
291
|
async fail(candidate) {
|
|
677
|
-
|
|
678
|
-
|
|
679
|
-
|
|
680
|
-
) {
|
|
681
|
-
throw new ToolCallReceiptLeaseLostError(
|
|
682
|
-
candidate.resultReceiptKey,
|
|
683
|
-
);
|
|
684
|
-
}
|
|
685
|
-
await new Promise<void>((resolve, reject) => {
|
|
686
|
-
queuedFailures.push({
|
|
687
|
-
memberId,
|
|
688
|
-
error: candidate.error,
|
|
689
|
-
resolve,
|
|
690
|
-
reject,
|
|
691
|
-
});
|
|
692
|
-
if (!failureFlushScheduled) {
|
|
693
|
-
failureFlushScheduled = true;
|
|
694
|
-
setTimeout(() => void flushFailures(), 0);
|
|
695
|
-
}
|
|
696
|
-
});
|
|
292
|
+
assert(candidate);
|
|
293
|
+
owned.delete(group.resultReceiptKey);
|
|
294
|
+
rejectGroup(group, candidate.error);
|
|
697
295
|
},
|
|
698
296
|
};
|
|
699
297
|
}
|
|
@@ -701,33 +299,35 @@ export function createToolCallReceiptCohortJob(input: {
|
|
|
701
299
|
return {
|
|
702
300
|
member(memberId) {
|
|
703
301
|
const state = states.get(memberId);
|
|
704
|
-
if (!state)
|
|
302
|
+
if (!state) {
|
|
705
303
|
throw new Error(
|
|
706
304
|
`Unknown Tool Call Receipt Cohort member ${memberId}.`,
|
|
707
305
|
);
|
|
306
|
+
}
|
|
708
307
|
return state;
|
|
709
308
|
},
|
|
710
|
-
complete,
|
|
711
|
-
fail
|
|
712
|
-
|
|
713
|
-
|
|
714
|
-
|
|
715
|
-
|
|
716
|
-
|
|
717
|
-
|
|
718
|
-
|
|
719
|
-
|
|
720
|
-
|
|
721
|
-
|
|
722
|
-
|
|
723
|
-
|
|
724
|
-
|
|
725
|
-
|
|
726
|
-
return
|
|
309
|
+
complete: publish,
|
|
310
|
+
async fail(entries) {
|
|
311
|
+
for (const entry of entries) {
|
|
312
|
+
const state = states.get(entry.memberId);
|
|
313
|
+
if (!state || state.kind !== 'owned') {
|
|
314
|
+
throw new ToolCallReceiptLeaseLostError(entry.memberId);
|
|
315
|
+
}
|
|
316
|
+
const groupOwned = owned.get(state.resultReceiptKey);
|
|
317
|
+
if (!groupOwned) {
|
|
318
|
+
throw new ToolCallReceiptLeaseLostError(state.resultReceiptKey);
|
|
319
|
+
}
|
|
320
|
+
owned.delete(state.resultReceiptKey);
|
|
321
|
+
rejectGroup(groupOwned.group, entry.error);
|
|
322
|
+
}
|
|
323
|
+
},
|
|
324
|
+
async heartbeat() {
|
|
325
|
+
return owned.size === 0 ? 'terminal' : 'active';
|
|
727
326
|
},
|
|
728
|
-
|
|
729
|
-
|
|
327
|
+
startHeartbeat() {
|
|
328
|
+
return { stop: () => undefined, assertOwned: () => undefined };
|
|
730
329
|
},
|
|
330
|
+
async settle() {},
|
|
731
331
|
};
|
|
732
332
|
},
|
|
733
333
|
};
|
|
@@ -737,7 +337,6 @@ type Group = {
|
|
|
737
337
|
resultReceiptKey: string;
|
|
738
338
|
members: ToolCallReceiptCohortMember[];
|
|
739
339
|
force: boolean;
|
|
740
|
-
forceFailedRefresh: boolean;
|
|
741
340
|
};
|
|
742
341
|
|
|
743
342
|
type OwnedGroup = { group: Group; lease: ToolCallReceiptLease };
|
|
@@ -748,6 +347,12 @@ type Deferred<T> = {
|
|
|
748
347
|
reject(error: unknown): void;
|
|
749
348
|
};
|
|
750
349
|
|
|
350
|
+
type PendingPublication = {
|
|
351
|
+
entries: Array<{ memberId: string; result: ToolExecuteResult }>;
|
|
352
|
+
resolve(value: Map<string, ToolExecuteResult>): void;
|
|
353
|
+
reject(error: unknown): void;
|
|
354
|
+
};
|
|
355
|
+
|
|
751
356
|
function groupMembers(
|
|
752
357
|
members: ToolCallReceiptCohortMember[],
|
|
753
358
|
): Map<string, Group> {
|
|
@@ -755,8 +360,9 @@ function groupMembers(
|
|
|
755
360
|
const memberIds = new Set<string>();
|
|
756
361
|
for (const member of members) {
|
|
757
362
|
const memberId = required(member.memberId, 'member id');
|
|
758
|
-
if (memberIds.has(memberId))
|
|
363
|
+
if (memberIds.has(memberId)) {
|
|
759
364
|
throw new Error(`Duplicate Tool Call Receipt Cohort member ${memberId}.`);
|
|
365
|
+
}
|
|
760
366
|
memberIds.add(memberId);
|
|
761
367
|
const resultReceiptKey = required(
|
|
762
368
|
member.resultReceiptKey,
|
|
@@ -766,18 +372,11 @@ function groupMembers(
|
|
|
766
372
|
resultReceiptKey,
|
|
767
373
|
members: [],
|
|
768
374
|
force: false,
|
|
769
|
-
forceFailedRefresh: false,
|
|
770
375
|
};
|
|
771
376
|
group.members.push({ ...member, memberId, resultReceiptKey });
|
|
772
377
|
group.force ||= member.force === true;
|
|
773
|
-
group.forceFailedRefresh ||= member.forceFailedRefresh === true;
|
|
774
378
|
groups.set(resultReceiptKey, group);
|
|
775
379
|
}
|
|
776
|
-
for (const group of groups.values()) {
|
|
777
|
-
// Full force dominates failed-only refresh for every duplicate member of
|
|
778
|
-
// the same content-addressed receipt, just as the current map grouping.
|
|
779
|
-
if (group.force) group.forceFailedRefresh = false;
|
|
780
|
-
}
|
|
781
380
|
return groups;
|
|
782
381
|
}
|
|
783
382
|
|
|
@@ -795,8 +394,6 @@ function deferredFor(
|
|
|
795
394
|
reject = rejectPromise;
|
|
796
395
|
},
|
|
797
396
|
);
|
|
798
|
-
// A receipt can fail before a same-run follower attaches. Do not create an
|
|
799
|
-
// unhandled rejection while preserving the rejection for real followers.
|
|
800
397
|
void promise.catch(() => undefined);
|
|
801
398
|
value = { promise, resolve, reject };
|
|
802
399
|
deferred.set(group.resultReceiptKey, value);
|
|
@@ -812,20 +409,12 @@ function ownedEntries<T extends { memberId: string }>(
|
|
|
812
409
|
for (const entry of entries) {
|
|
813
410
|
const state = states.get(entry.memberId);
|
|
814
411
|
if (!state || state.kind !== 'owned') {
|
|
815
|
-
throw new
|
|
816
|
-
`Tool Call Receipt Cohort member ${entry.memberId} does not own a receipt.`,
|
|
817
|
-
);
|
|
412
|
+
throw new ToolCallReceiptLeaseLostError(entry.memberId);
|
|
818
413
|
}
|
|
819
414
|
const groupOwned = owned.get(state.resultReceiptKey);
|
|
820
415
|
if (!groupOwned || !sameLease(groupOwned.lease, state.lease)) {
|
|
821
416
|
throw new ToolCallReceiptLeaseLostError(state.resultReceiptKey);
|
|
822
417
|
}
|
|
823
|
-
const existing = byKey.get(groupOwned.group.resultReceiptKey);
|
|
824
|
-
if (existing && existing.memberId !== entry.memberId) {
|
|
825
|
-
throw new Error(
|
|
826
|
-
`Tool Call Receipt Cohort received two terminal outcomes for ${groupOwned.group.resultReceiptKey}.`,
|
|
827
|
-
);
|
|
828
|
-
}
|
|
829
418
|
byKey.set(groupOwned.group.resultReceiptKey, { ...entry, ...groupOwned });
|
|
830
419
|
}
|
|
831
420
|
return [...byKey.values()];
|
|
@@ -834,49 +423,7 @@ function ownedEntries<T extends { memberId: string }>(
|
|
|
834
423
|
function byKey(
|
|
835
424
|
records: ToolCallReceiptRecord[],
|
|
836
425
|
): Map<string, ToolCallReceiptRecord> {
|
|
837
|
-
|
|
838
|
-
for (const record of records) result.set(record.resultReceiptKey, record);
|
|
839
|
-
return result;
|
|
840
|
-
}
|
|
841
|
-
|
|
842
|
-
function isCompleted(
|
|
843
|
-
record: ToolCallReceiptRecord | undefined,
|
|
844
|
-
): record is ToolCallReceiptRecord & { result: ToolExecuteResult } {
|
|
845
|
-
if (!record) return false;
|
|
846
|
-
return (
|
|
847
|
-
(record.status === 'completed' || record.status === 'skipped') &&
|
|
848
|
-
record.result !== undefined
|
|
849
|
-
);
|
|
850
|
-
}
|
|
851
|
-
|
|
852
|
-
function isOwned(record: ToolCallReceiptRecord, ownerRunId: string): boolean {
|
|
853
|
-
return (
|
|
854
|
-
record.claimState !== 'existing' &&
|
|
855
|
-
Boolean(record.lease) &&
|
|
856
|
-
record.lease?.ownerRunId === ownerRunId &&
|
|
857
|
-
(record.status === 'queued' ||
|
|
858
|
-
record.status === 'pending' ||
|
|
859
|
-
record.status === 'running')
|
|
860
|
-
);
|
|
861
|
-
}
|
|
862
|
-
|
|
863
|
-
function isLiveLeased(record: ToolCallReceiptRecord): boolean {
|
|
864
|
-
if (
|
|
865
|
-
record.status !== 'queued' &&
|
|
866
|
-
record.status !== 'pending' &&
|
|
867
|
-
record.status !== 'running'
|
|
868
|
-
)
|
|
869
|
-
return false;
|
|
870
|
-
if (!record.lease) return false;
|
|
871
|
-
const expiresAt = Date.parse(record.leaseExpiresAt ?? '');
|
|
872
|
-
return !Number.isFinite(expiresAt) || expiresAt > Date.now();
|
|
873
|
-
}
|
|
874
|
-
|
|
875
|
-
function isSameLease(
|
|
876
|
-
record: ToolCallReceiptRecord,
|
|
877
|
-
lease: ToolCallReceiptLease,
|
|
878
|
-
): boolean {
|
|
879
|
-
return Boolean(record.lease) && sameLease(record.lease!, lease);
|
|
426
|
+
return new Map(records.map((record) => [record.resultReceiptKey, record]));
|
|
880
427
|
}
|
|
881
428
|
|
|
882
429
|
function sameLease(
|
|
@@ -888,17 +435,8 @@ function sameLease(
|
|
|
888
435
|
|
|
889
436
|
function required(value: string, name: string): string {
|
|
890
437
|
const normalized = value?.trim();
|
|
891
|
-
if (!normalized)
|
|
438
|
+
if (!normalized) {
|
|
892
439
|
throw new Error(`Tool Call Receipt Cohort requires a ${name}.`);
|
|
440
|
+
}
|
|
893
441
|
return normalized;
|
|
894
442
|
}
|
|
895
|
-
|
|
896
|
-
function positive(value: number, name: string): number {
|
|
897
|
-
if (!Number.isFinite(value) || value <= 0)
|
|
898
|
-
throw new Error(`Tool Call Receipt Cohort ${name} must be positive.`);
|
|
899
|
-
return Math.floor(value);
|
|
900
|
-
}
|
|
901
|
-
|
|
902
|
-
async function defaultSleep(milliseconds: number): Promise<void> {
|
|
903
|
-
await new Promise<void>((resolve) => setTimeout(resolve, milliseconds));
|
|
904
|
-
}
|