deepline 0.2.11 → 0.2.13

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.
@@ -160,7 +160,7 @@ export const SDK_RELEASE = {
160
160
  // 0.2.0 makes Dataset Handles uniformly async-only after 0.1.320 briefly
161
161
  // exposed storage-dependent synchronous access. This deliberate minor
162
162
  // release keeps lazy paging semantics independent of row residency.
163
- version: '0.2.11',
163
+ version: '0.2.13',
164
164
  contracts: {
165
165
  api: {
166
166
  name: 'sdk-http-api',
@@ -2276,23 +2276,16 @@ export class PlayContextImpl {
2276
2276
  if (!this.#options.heartbeatRuntimeStepReceipts) {
2277
2277
  return await this.assertRuntimeToolReceiptOwnership(requests);
2278
2278
  }
2279
- const byLeaseId = new Map<string, string[]>();
2280
- for (const target of targets) {
2281
- const keys = byLeaseId.get(target.leaseId) ?? [];
2282
- keys.push(target.receiptKey);
2283
- byLeaseId.set(target.leaseId, keys);
2284
- }
2285
- await Promise.all(
2286
- [...byLeaseId].map(async ([leaseId, keys]) => {
2287
- const receipts = await this.#options.heartbeatRuntimeStepReceipts!({
2288
- runId: this.currentReceiptOwnerRunId,
2289
- runAttempt: this.currentRunAttempt,
2290
- leaseId,
2291
- keys,
2292
- });
2293
- this.assertRuntimeToolReceiptHeartbeatResult(keys, leaseId, receipts);
2294
- }),
2295
- );
2279
+ const uniqueTargets = [
2280
+ ...new Map(targets.map((target) => [target.receiptKey, target])).values(),
2281
+ ];
2282
+ const receipts = await this.#options.heartbeatRuntimeStepReceipts({
2283
+ runId: this.currentReceiptOwnerRunId,
2284
+ runAttempt: this.currentRunAttempt,
2285
+ keys: uniqueTargets.map((target) => target.receiptKey),
2286
+ leaseIds: uniqueTargets.map((target) => target.leaseId),
2287
+ });
2288
+ this.assertRuntimeToolReceiptHeartbeatResults(uniqueTargets, receipts);
2296
2289
  }
2297
2290
 
2298
2291
  private async flushRuntimeToolOwnershipAssertionBatch(): Promise<void> {
@@ -2303,26 +2296,24 @@ export class PlayContextImpl {
2303
2296
 
2304
2297
  if (this.#options.heartbeatRuntimeStepReceipts) {
2305
2298
  try {
2306
- const byLeaseId = new Map<string, string[]>();
2307
- for (const target of targets) {
2308
- const keys = byLeaseId.get(target.leaseId) ?? [];
2309
- keys.push(target.receiptKey);
2310
- byLeaseId.set(target.leaseId, keys);
2311
- }
2299
+ const uniqueTargets = [
2300
+ ...new Map(
2301
+ targets.map((target) => [target.receiptKey, target]),
2302
+ ).values(),
2303
+ ];
2304
+ const receipts = await this.#options.heartbeatRuntimeStepReceipts({
2305
+ runId: this.currentReceiptOwnerRunId,
2306
+ runAttempt: this.currentRunAttempt,
2307
+ keys: uniqueTargets.map((target) => target.receiptKey),
2308
+ leaseIds: uniqueTargets.map((target) => target.leaseId),
2309
+ });
2312
2310
  const renewed = new Map<string, RuntimeStepReceipt | null>();
2313
- await Promise.all(
2314
- [...byLeaseId].map(async ([leaseId, keys]) => {
2315
- const receipts = await this.#options.heartbeatRuntimeStepReceipts!({
2316
- runId: this.currentReceiptOwnerRunId,
2317
- runAttempt: this.currentRunAttempt,
2318
- leaseId,
2319
- keys,
2320
- });
2321
- for (let index = 0; index < keys.length; index += 1) {
2322
- renewed.set(keys[index]!, receipts[index] ?? null);
2323
- }
2324
- }),
2325
- );
2311
+ for (let index = 0; index < uniqueTargets.length; index += 1) {
2312
+ renewed.set(
2313
+ uniqueTargets[index]!.receiptKey,
2314
+ receipts[index] ?? null,
2315
+ );
2316
+ }
2326
2317
  for (const assertion of assertions) {
2327
2318
  const lost = assertion.targets.find((target) => {
2328
2319
  const receipt = renewed.get(target.receiptKey);
@@ -2385,21 +2376,21 @@ export class PlayContextImpl {
2385
2376
  for (const assertion of assertions) assertion.reject(error);
2386
2377
  }
2387
2378
 
2388
- private assertRuntimeToolReceiptHeartbeatResult(
2389
- keys: string[],
2390
- leaseId: string,
2379
+ private assertRuntimeToolReceiptHeartbeatResults(
2380
+ targets: Array<{ receiptKey: string; leaseId: string }>,
2391
2381
  receipts: Array<RuntimeStepReceipt | null>,
2392
2382
  ): void {
2393
- for (let index = 0; index < keys.length; index += 1) {
2383
+ for (let index = 0; index < targets.length; index += 1) {
2384
+ const target = targets[index]!;
2394
2385
  const receipt = this.normalizeRuntimeStepReceipt(
2395
- keys[index] ?? '',
2386
+ target.receiptKey,
2396
2387
  receipts[index],
2397
2388
  );
2398
- if (!this.runtimeToolReceiptStillOwned(receipt, leaseId)) {
2389
+ if (!this.runtimeToolReceiptStillOwned(receipt, target.leaseId)) {
2399
2390
  throw new RuntimeReceiptLeaseLostError({
2400
- receiptKey: keys[index] ?? 'unknown',
2391
+ receiptKey: target.receiptKey,
2401
2392
  runId: this.currentReceiptOwnerRunId,
2402
- leaseId,
2393
+ leaseId: target.leaseId,
2403
2394
  });
2404
2395
  }
2405
2396
  }
@@ -8040,58 +8031,50 @@ export class PlayContextImpl {
8040
8031
  this.toolCallResolvers.has(request.callId),
8041
8032
  );
8042
8033
  if (unsettled.length === 0) return 'terminal';
8043
- const byLeaseId = new Map<string, Map<string, ToolCallRequest[]>>();
8034
+ const byReceiptKey = new Map<string, ToolCallRequest[]>();
8044
8035
  for (const request of unsettled) {
8045
- const leaseId = request.receiptLeaseId!;
8046
- const byReceiptKey = byLeaseId.get(leaseId) ?? new Map();
8047
8036
  const receiptKey = request.receiptKey!;
8048
8037
  const group = byReceiptKey.get(receiptKey) ?? [];
8049
8038
  group.push(request);
8050
8039
  byReceiptKey.set(receiptKey, group);
8051
- byLeaseId.set(leaseId, byReceiptKey);
8052
8040
  }
8053
- await Promise.all(
8054
- [...byLeaseId].map(async ([leaseId, byReceiptKey]) => {
8055
- // One content-addressed receipt can have many same-run followers.
8056
- // The store renews unique receipt rows, not request positions, so
8057
- // duplicate keys in one heartbeat would yield null duplicate
8058
- // positions and falsely stop the supervisor as if ownership were
8059
- // lost.
8060
- const entries = [...byReceiptKey];
8061
- const keys = entries.map(([receiptKey]) => receiptKey);
8062
- const receipts = await heartbeatReceipts({
8041
+ // One content-addressed receipt can have many same-run followers. The
8042
+ // store renews unique receipt rows, not request positions, so dedupe by
8043
+ // key while preserving the matching per-row lease id.
8044
+ const entries = [...byReceiptKey];
8045
+ const keys = entries.map(([receiptKey]) => receiptKey);
8046
+ const leaseIds = entries.map(
8047
+ ([, requests]) => requests[0]!.receiptLeaseId!,
8048
+ );
8049
+ const receipts = await heartbeatReceipts({
8050
+ runId: this.currentReceiptOwnerRunId,
8051
+ runAttempt: this.currentRunAttempt,
8052
+ keys,
8053
+ leaseIds,
8054
+ });
8055
+ for (let index = 0; index < entries.length; index += 1) {
8056
+ const [receiptKey, requests] = entries[index]!;
8057
+ const leaseId = leaseIds[index]!;
8058
+ // Completion can race this bulk heartbeat response. A settled
8059
+ // receipt group no longer needs ownership and must not turn that
8060
+ // race into a false lease-loss failure.
8061
+ if (
8062
+ !requests.some((request) =>
8063
+ this.toolCallResolvers.has(request.callId),
8064
+ )
8065
+ ) {
8066
+ continue;
8067
+ }
8068
+ if (
8069
+ !this.runtimeToolReceiptStillOwned(receipts[index] ?? null, leaseId)
8070
+ ) {
8071
+ throw new RuntimeReceiptLeaseLostError({
8072
+ receiptKey,
8063
8073
  runId: this.currentReceiptOwnerRunId,
8064
- runAttempt: this.currentRunAttempt,
8065
8074
  leaseId,
8066
- keys,
8067
8075
  });
8068
- for (let index = 0; index < entries.length; index += 1) {
8069
- const [receiptKey, requests] = entries[index]!;
8070
- // Completion can race this bulk heartbeat response. A settled
8071
- // receipt group no longer needs ownership and must not turn that
8072
- // race into a false lease-loss failure.
8073
- if (
8074
- !requests.some((request) =>
8075
- this.toolCallResolvers.has(request.callId),
8076
- )
8077
- ) {
8078
- continue;
8079
- }
8080
- if (
8081
- !this.runtimeToolReceiptStillOwned(
8082
- receipts[index] ?? null,
8083
- leaseId,
8084
- )
8085
- ) {
8086
- throw new RuntimeReceiptLeaseLostError({
8087
- receiptKey,
8088
- runId: this.currentReceiptOwnerRunId,
8089
- leaseId,
8090
- });
8091
- }
8092
- }
8093
- }),
8094
- );
8076
+ }
8077
+ }
8095
8078
  return 'active';
8096
8079
  },
8097
8080
  isLeaseLost: (error) => error instanceof RuntimeReceiptLeaseLostError,
@@ -204,7 +204,8 @@ export interface HeartbeatRuntimeStepReceiptsInput {
204
204
  keys: string[];
205
205
  runId: string;
206
206
  runAttempt?: number | null;
207
- leaseId: string;
207
+ leaseId?: string;
208
+ leaseIds?: string[];
208
209
  }
209
210
 
210
211
  export interface CsvOptions {
@@ -215,7 +215,8 @@ export type RuntimeReceiptAction =
215
215
  playName: string;
216
216
  runId: string;
217
217
  runAttempt?: number | null;
218
- leaseId: string;
218
+ leaseId?: string;
219
+ leaseIds?: string[];
219
220
  keys: string[];
220
221
  };
221
222
 
@@ -5799,15 +5799,30 @@ export async function heartbeatRuntimeWorkReceipts(
5799
5799
  playName: string;
5800
5800
  runId: string;
5801
5801
  runAttempt?: number | null;
5802
- leaseId: string;
5802
+ leaseId?: string;
5803
+ leaseIds?: string[];
5803
5804
  keys: string[];
5804
5805
  leaseTtlMs?: number | null;
5805
5806
  },
5806
5807
  ): Promise<Array<WorkReceipt | null>> {
5807
- const keys = input.keys.map((key) => key.trim()).filter(Boolean);
5808
+ if (
5809
+ input.leaseIds !== undefined &&
5810
+ input.leaseIds.length !== input.keys.length
5811
+ ) {
5812
+ throw new Error(
5813
+ `Runtime receipt bulk heartbeat requires one lease ID per key. Received ${input.leaseIds.length} lease IDs for ${input.keys.length} keys.`,
5814
+ );
5815
+ }
5816
+ const entries = input.keys
5817
+ .map((key, index) => ({
5818
+ key: key.trim(),
5819
+ leaseId: input.leaseIds?.[index]?.trim() || input.leaseId?.trim() || '',
5820
+ }))
5821
+ .filter((entry) => Boolean(entry.key));
5822
+ const keys = entries.map((entry) => entry.key);
5808
5823
  if (keys.length === 0) return [];
5809
- const leaseId = input.leaseId.trim();
5810
- if (!leaseId) return keys.map(() => null);
5824
+ const leaseIds = entries.map((entry) => entry.leaseId);
5825
+ if (leaseIds.some((leaseId) => !leaseId)) return keys.map(() => null);
5811
5826
  const runAttempt = normalizeRuntimeRunAttempt(input.runAttempt);
5812
5827
  const leaseTtlMs = normalizeRuntimeLeaseTtlMs(
5813
5828
  input.leaseTtlMs,
@@ -5824,8 +5839,10 @@ export async function heartbeatRuntimeWorkReceipts(
5824
5839
  const { rows } = await client.query<Record<string, unknown>>(
5825
5840
  `
5826
5841
  WITH input_keys AS (
5827
- SELECT key_values.key_hex, key_values.ord
5842
+ SELECT key_values.key_hex, lease_values.lease_id, key_values.ord
5828
5843
  FROM unnest($1::text[]) WITH ORDINALITY AS key_values(key_hex, ord)
5844
+ JOIN unnest($3::text[]) WITH ORDINALITY AS lease_values(lease_id, ord)
5845
+ ON lease_values.ord = key_values.ord
5829
5846
  ),
5830
5847
  renewed AS (
5831
5848
  UPDATE ${workReceiptTable(session)} AS target
@@ -5837,7 +5854,7 @@ export async function heartbeatRuntimeWorkReceipts(
5837
5854
  receiptTable: 'target',
5838
5855
  ownerRunIdSql: '$2::text',
5839
5856
  ownerRunAttemptSql: '$5',
5840
- leaseIdSql: '$3::text',
5857
+ leaseIdSql: 'input_keys.lease_id',
5841
5858
  })}
5842
5859
  RETURNING target.k,
5843
5860
  target.status,
@@ -5888,7 +5905,7 @@ export async function heartbeatRuntimeWorkReceipts(
5888
5905
  [
5889
5906
  keys.map(workReceiptKeyHex),
5890
5907
  input.runId,
5891
- leaseId,
5908
+ leaseIds,
5892
5909
  leaseTtlMs,
5893
5910
  runAttempt,
5894
5911
  ],
@@ -1,4 +1,4 @@
1
- export const PLAY_RUNTIME_CONTRACT = 3;
1
+ export const PLAY_RUNTIME_CONTRACT = 4;
2
2
  export const PLAY_RUNTIME_CONTRACT_MIN_SUPPORTED = 1;
3
3
  export const PLAY_RUNTIME_CONTRACT_HEADER = 'x-deepline-runtime-contract';
4
4
  /**
@@ -25,6 +25,13 @@ export function resolvePlayRuntimeTransportAttemptId(
25
25
  }
26
26
 
27
27
  export const RUNTIME_CONTRACT_CHANGELOG = [
28
+ {
29
+ contract: 4,
30
+ changed:
31
+ 'Runtime receipt heartbeat requests can carry one positional lease ID per receipt key.',
32
+ compatOwner: 'runtime',
33
+ compatRemoval: 'contract 3 runtime lane drain',
34
+ },
28
35
  {
29
36
  contract: 3,
30
37
  changed:
@@ -236,13 +236,13 @@ export class RuntimeReceiptStoreAdapter {
236
236
  ),
237
237
  heartbeatRuntimeStepReceipts: (input) =>
238
238
  writeMany(
239
- input.keys.map((key) => ({
239
+ input.keys.map((key, index) => ({
240
240
  kind: 'heartbeat' as const,
241
241
  input: {
242
242
  key,
243
243
  runId: input.runId,
244
244
  runAttempt: input.runAttempt,
245
- leaseId: input.leaseId,
245
+ leaseId: input.leaseIds?.[index] ?? input.leaseId ?? '',
246
246
  },
247
247
  })),
248
248
  ),
@@ -387,7 +387,6 @@ function receiptCommandBatchKey(command: ReceiptCommand): string {
387
387
  command.kind,
388
388
  command.input.runId,
389
389
  command.input.runAttempt ?? null,
390
- command.input.leaseId,
391
390
  ]);
392
391
  case 'acquire_execution_lock':
393
392
  case 'release_execution_lock':
@@ -491,11 +490,15 @@ async function sendReceiptCommands(input: {
491
490
  ReceiptCommand,
492
491
  { kind: 'heartbeat' }
493
492
  >[];
493
+ const leaseIds = commands.map((command) => command.input.leaseId);
494
+ const sharedLeaseId = leaseIds.every((leaseId) => leaseId === leaseIds[0])
495
+ ? leaseIds[0]
496
+ : undefined;
494
497
  return await heartbeatRuntimeStepReceiptsViaAppRuntime(input.context, {
495
498
  playName: input.playName,
496
499
  runId: first.input.runId,
497
500
  runAttempt: first.input.runAttempt,
498
- leaseId: first.input.leaseId,
501
+ ...(sharedLeaseId ? { leaseId: sharedLeaseId } : { leaseIds }),
499
502
  keys: commands.map((command) => command.input.key),
500
503
  });
501
504
  }
package/dist/cli/index.js CHANGED
@@ -1040,7 +1040,7 @@ var SDK_RELEASE = {
1040
1040
  // 0.2.0 makes Dataset Handles uniformly async-only after 0.1.320 briefly
1041
1041
  // exposed storage-dependent synchronous access. This deliberate minor
1042
1042
  // release keeps lazy paging semantics independent of row residency.
1043
- version: "0.2.11",
1043
+ version: "0.2.13",
1044
1044
  contracts: {
1045
1045
  api: {
1046
1046
  name: "sdk-http-api",
@@ -1025,7 +1025,7 @@ var SDK_RELEASE = {
1025
1025
  // 0.2.0 makes Dataset Handles uniformly async-only after 0.1.320 briefly
1026
1026
  // exposed storage-dependent synchronous access. This deliberate minor
1027
1027
  // release keeps lazy paging semantics independent of row residency.
1028
- version: "0.2.11",
1028
+ version: "0.2.13",
1029
1029
  contracts: {
1030
1030
  api: {
1031
1031
  name: "sdk-http-api",
package/dist/index.js CHANGED
@@ -763,7 +763,7 @@ var SDK_RELEASE = {
763
763
  // 0.2.0 makes Dataset Handles uniformly async-only after 0.1.320 briefly
764
764
  // exposed storage-dependent synchronous access. This deliberate minor
765
765
  // release keeps lazy paging semantics independent of row residency.
766
- version: "0.2.11",
766
+ version: "0.2.13",
767
767
  contracts: {
768
768
  api: {
769
769
  name: "sdk-http-api",
package/dist/index.mjs CHANGED
@@ -689,7 +689,7 @@ var SDK_RELEASE = {
689
689
  // 0.2.0 makes Dataset Handles uniformly async-only after 0.1.320 briefly
690
690
  // exposed storage-dependent synchronous access. This deliberate minor
691
691
  // release keeps lazy paging semantics independent of row residency.
692
- version: "0.2.11",
692
+ version: "0.2.13",
693
693
  contracts: {
694
694
  api: {
695
695
  name: "sdk-http-api",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "deepline",
3
- "version": "0.2.11",
3
+ "version": "0.2.13",
4
4
  "description": "Deepline SDK + CLI — B2B data enrichment powered by durable cloud execution",
5
5
  "license": "MIT",
6
6
  "repository": {