deepline 0.2.12 → 0.2.14

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.
Files changed (39) hide show
  1. package/dist/bundling-sources/sdk/src/client.ts +159 -3
  2. package/dist/bundling-sources/sdk/src/index.ts +3 -0
  3. package/dist/bundling-sources/sdk/src/play.ts +150 -691
  4. package/dist/bundling-sources/sdk/src/release.ts +1 -1
  5. package/dist/bundling-sources/shared_libs/play-runtime/child-execution-strategy.ts +7 -1
  6. package/dist/bundling-sources/shared_libs/play-runtime/context.ts +319 -138
  7. package/dist/bundling-sources/shared_libs/play-runtime/csv-rename.ts +10 -6
  8. package/dist/bundling-sources/shared_libs/play-runtime/ctx-types.ts +34 -52
  9. package/dist/bundling-sources/shared_libs/play-runtime/durable-call-cache.ts +11 -22
  10. package/dist/bundling-sources/shared_libs/play-runtime/durable-call-policy.ts +34 -0
  11. package/dist/bundling-sources/shared_libs/play-runtime/play-call-execution.ts +2 -1
  12. package/dist/bundling-sources/shared_libs/play-runtime/run-ledger-projection-contract.ts +95 -0
  13. package/dist/bundling-sources/shared_libs/play-runtime/runtime-actions.ts +2 -1
  14. package/dist/bundling-sources/shared_libs/play-runtime/runtime-api.ts +24 -7
  15. package/dist/bundling-sources/shared_libs/play-runtime/runtime-contract.ts +8 -1
  16. package/dist/bundling-sources/shared_libs/play-runtime/runtime-receipt-store-adapter.ts +7 -4
  17. package/dist/bundling-sources/shared_libs/play-runtime/secret-capability.ts +23 -15
  18. package/dist/bundling-sources/shared_libs/plays/artifact-types.ts +3 -0
  19. package/dist/bundling-sources/shared_libs/plays/authoring-contract.ts +2222 -0
  20. package/dist/bundling-sources/shared_libs/plays/bundling/index.ts +3 -2
  21. package/dist/bundling-sources/shared_libs/plays/compiler-manifest.ts +2 -0
  22. package/dist/bundling-sources/shared_libs/plays/contracts.ts +16 -0
  23. package/dist/bundling-sources/shared_libs/plays/input-contract-definition.ts +28 -0
  24. package/dist/bundling-sources/shared_libs/plays/input-contract.ts +3 -3
  25. package/dist/cli/index.js +5231 -830
  26. package/dist/cli/index.mjs +5178 -762
  27. package/dist/compiler-manifest-xFkbJX2B.d.mts +2517 -0
  28. package/dist/compiler-manifest-xFkbJX2B.d.ts +2517 -0
  29. package/dist/index.d.mts +126 -1014
  30. package/dist/index.d.ts +126 -1014
  31. package/dist/index.js +88 -8
  32. package/dist/index.mjs +88 -8
  33. package/dist/plays/bundle-play-file.d.mts +5 -8
  34. package/dist/plays/bundle-play-file.d.ts +5 -8
  35. package/dist/plays/bundle-play-file.mjs +3844 -3
  36. package/package.json +1 -1
  37. package/dist/bundling-sources/shared_libs/plays/source-metadata.ts +0 -240
  38. package/dist/tool-execution-error-4-rhemLQ.d.mts +0 -446
  39. package/dist/tool-execution-error-4-rhemLQ.d.ts +0 -446
@@ -142,7 +142,17 @@ import {
142
142
  buildDurableToolCallCacheKey,
143
143
  buildDurableToolProviderIdempotencyKey,
144
144
  buildDurableToolReceiptPrefix,
145
+ resolveDurableCallCachePolicy,
145
146
  } from './durable-call-cache';
147
+ import {
148
+ PLAY_AUTHORING_CONTRACT_EDITION,
149
+ normalizePlayAuthoringCustomerDbStatement,
150
+ validateOptionalPlayAuthoringField,
151
+ validatePlayAuthoringField,
152
+ type PlayAuthoringContractEdition,
153
+ type PlaySqlQuery,
154
+ type PlayAuthoringRuntimeContext,
155
+ } from '../plays/authoring-contract';
146
156
  import {
147
157
  RuntimeReceiptLeaseLostError,
148
158
  RuntimeReceiptWaitTimeoutError,
@@ -236,7 +246,7 @@ import {
236
246
  import type {
237
247
  CsvOptions,
238
248
  RowState,
239
- DatasetOptions,
249
+ RuntimeDatasetOptions,
240
250
  ToolCallRequest,
241
251
  ToolBatchResult,
242
252
  ContextOptions,
@@ -248,7 +258,7 @@ import type {
248
258
  MapFieldDefinition,
249
259
  MapFieldResolver,
250
260
  ToolCallOptions,
251
- StepOptions,
261
+ RuntimeStepOptions,
252
262
  FetchOptions,
253
263
  ResolvedPlayExecution,
254
264
  PlayFetchResponse,
@@ -304,6 +314,8 @@ type InlineCompositionStore = {
304
314
  staticPipeline: ContextOptions['staticPipeline'];
305
315
  /** Immutable error contract pinned by the child play artifact. */
306
316
  toolErrorSchemaVersion: ToolExecutionErrorSchemaVersion;
317
+ /** Immutable authoring semantics pinned by the child play artifact. */
318
+ authoringContractEdition: PlayAuthoringContractEdition;
307
319
  };
308
320
  const inlineCompositionContext =
309
321
  new AsyncLocalStorage<InlineCompositionStore>();
@@ -575,13 +587,21 @@ export async function reconcileNodeRuntimeMapResultsWithPersistedSheet(input: {
575
587
  export function resolveToolRuntimeTimeoutMs(
576
588
  toolId: string,
577
589
  requestedTimeoutMs?: number,
590
+ authoringContractEdition: PlayAuthoringContractEdition = PLAY_AUTHORING_CONTRACT_EDITION,
578
591
  ): number | undefined {
579
- if (
580
- typeof requestedTimeoutMs === 'number' &&
581
- Number.isFinite(requestedTimeoutMs) &&
582
- requestedTimeoutMs > 0
583
- ) {
584
- return Math.max(1, Math.ceil(requestedTimeoutMs));
592
+ if (requestedTimeoutMs !== undefined) {
593
+ if (authoringContractEdition >= 2) {
594
+ validatePlayAuthoringField(
595
+ 'ctx.tools.execute.timeoutMs',
596
+ requestedTimeoutMs,
597
+ );
598
+ return requestedTimeoutMs;
599
+ }
600
+ // Edition 1 rounded positive finite values and treated every other value
601
+ // as omitted. Preserve that behavior for already-published artifacts.
602
+ if (Number.isFinite(requestedTimeoutMs) && requestedTimeoutMs > 0) {
603
+ return Math.max(1, Math.ceil(requestedTimeoutMs));
604
+ }
585
605
  }
586
606
  const normalized = toolId.trim().toLowerCase();
587
607
  // Long-inference tools keep their explicit 15-minute budget. Every other tool
@@ -1135,7 +1155,7 @@ class RuntimeDatasetBuilder<T extends Record<string, unknown>> {
1135
1155
  private readonly builder: StepProgramDatasetBuilder<
1136
1156
  RuntimeStepProgramStep,
1137
1157
  RuntimeStepProgramStep['resolver'],
1138
- DatasetOptions<T>,
1158
+ RuntimeDatasetOptions<T>,
1139
1159
  Promise<PlayDataset<Record<string, unknown>>>
1140
1160
  >;
1141
1161
 
@@ -1181,7 +1201,7 @@ class RuntimeDatasetBuilder<T extends Record<string, unknown>> {
1181
1201
  }
1182
1202
 
1183
1203
  run(
1184
- options?: DatasetOptions<T>,
1204
+ options?: RuntimeDatasetOptions<T>,
1185
1205
  ): Promise<PlayDataset<Record<string, unknown>>> {
1186
1206
  return this.builder.run(options);
1187
1207
  }
@@ -1288,7 +1308,20 @@ function createPacingResolver(
1288
1308
  };
1289
1309
  }
1290
1310
 
1291
- export class PlayContextImpl {
1311
+ type ScalarPlayAuthoringRuntimeContext = Pick<
1312
+ PlayAuthoringRuntimeContext,
1313
+ | 'tools'
1314
+ | 'customerDb'
1315
+ | 'tool'
1316
+ | 'step'
1317
+ | 'fetch'
1318
+ | 'secrets'
1319
+ | 'runPlay'
1320
+ | 'log'
1321
+ | 'sleep'
1322
+ >;
1323
+
1324
+ export class PlayContextImpl implements ScalarPlayAuthoringRuntimeContext {
1292
1325
  private rowStates = new Map<number, RowState>();
1293
1326
  private toolCallQueue: ToolCallRequest[] = [];
1294
1327
  private pendingRuntimeToolOwnershipAssertions: Array<{
@@ -1423,7 +1456,7 @@ export class PlayContextImpl {
1423
1456
  input: Record<string, unknown>;
1424
1457
  description?: string;
1425
1458
  force?: boolean;
1426
- staleAfterSeconds?: number;
1459
+ staleAfterSeconds?: number | null;
1427
1460
  timeoutMs?: number;
1428
1461
  receiptWaitMs?: number;
1429
1462
  }): Promise<TOutput> => {
@@ -1432,20 +1465,37 @@ export class PlayContextImpl {
1432
1465
  'ctx.tools.execute requires a request object: ctx.tools.execute({ id, tool, input, description }).',
1433
1466
  );
1434
1467
  }
1468
+ validatePlayAuthoringField('ctx.tools.execute.id', request.id);
1469
+ validatePlayAuthoringField('ctx.tools.execute.tool', request.tool);
1470
+ validatePlayAuthoringField('ctx.tools.execute.input', request.input);
1471
+ if (request.description !== undefined) {
1472
+ validatePlayAuthoringField(
1473
+ 'ctx.tools.execute.description',
1474
+ request.description,
1475
+ );
1476
+ }
1477
+ if (request.force !== undefined) {
1478
+ validatePlayAuthoringField('ctx.tools.execute.force', request.force);
1479
+ }
1480
+ assertNoSecretTaint(request.input, 'ctx.tools.execute input');
1435
1481
  if (
1436
- typeof request.id !== 'string' ||
1437
- !request.id.trim() ||
1438
- typeof request.tool !== 'string' ||
1439
- !request.tool.trim() ||
1440
- !request.input ||
1441
- typeof request.input !== 'object' ||
1442
- Array.isArray(request.input)
1482
+ request.timeoutMs !== undefined &&
1483
+ this.currentAuthoringContractEdition >= 2
1443
1484
  ) {
1444
- throw new Error(
1445
- 'ctx.tools.execute({ id, tool, input }) requires a non-empty id, tool string, and input object.',
1485
+ validatePlayAuthoringField(
1486
+ 'ctx.tools.execute.timeoutMs',
1487
+ request.timeoutMs,
1488
+ );
1489
+ }
1490
+ if (
1491
+ request.receiptWaitMs !== undefined &&
1492
+ this.currentAuthoringContractEdition >= 2
1493
+ ) {
1494
+ validatePlayAuthoringField(
1495
+ 'ctx.tools.execute.receiptWaitMs',
1496
+ request.receiptWaitMs,
1446
1497
  );
1447
1498
  }
1448
- assertNoSecretTaint(request.input, 'ctx.tools.execute input');
1449
1499
  const force =
1450
1500
  request.force === true ||
1451
1501
  toolExecutionOverrides.getStore()?.force === true;
@@ -1478,19 +1528,55 @@ export class PlayContextImpl {
1478
1528
  },
1479
1529
  };
1480
1530
 
1531
+ async tool<TOutput = unknown>(
1532
+ key: string,
1533
+ toolId: string,
1534
+ input: Record<string, unknown>,
1535
+ options?: { description?: string },
1536
+ ): Promise<ToolExecuteResult<TOutput>> {
1537
+ validatePlayAuthoringField('ctx.tool.key', key);
1538
+ validatePlayAuthoringField('ctx.tool.tool', toolId);
1539
+ validatePlayAuthoringField('ctx.tool.input', input);
1540
+ if (options?.description !== undefined) {
1541
+ validatePlayAuthoringField(
1542
+ 'ctx.tool.options.description',
1543
+ options.description,
1544
+ );
1545
+ }
1546
+ return (await this.tools.execute<ToolExecuteResult<TOutput>>({
1547
+ id: key,
1548
+ tool: toolId,
1549
+ input,
1550
+ ...(options?.description ? { description: options.description } : {}),
1551
+ })) as ToolExecuteResult<TOutput>;
1552
+ }
1553
+
1481
1554
  async __deeplineRunWithForcedTools<T>(run: () => Promise<T>): Promise<T> {
1482
1555
  return await toolExecutionOverrides.run({ force: true }, run);
1483
1556
  }
1484
1557
  readonly customerDb = {
1485
1558
  query: async <TRow extends object = Record<string, unknown>>(
1486
- statement: string,
1559
+ statement: PlaySqlQuery | string,
1487
1560
  options?: { maxRows?: number; timeoutMs?: number },
1488
1561
  ): Promise<TRow[]> => {
1562
+ const sql = normalizePlayAuthoringCustomerDbStatement(statement);
1563
+ if (options?.maxRows !== undefined) {
1564
+ validatePlayAuthoringField(
1565
+ 'ctx.customerDb.query.options.maxRows',
1566
+ options.maxRows,
1567
+ );
1568
+ }
1569
+ if (options?.timeoutMs !== undefined) {
1570
+ validatePlayAuthoringField(
1571
+ 'ctx.customerDb.query.options.timeoutMs',
1572
+ options.timeoutMs,
1573
+ );
1574
+ }
1489
1575
  const result = (await this.tools.execute({
1490
1576
  id: `customer_db_query_${this.customerDbQueryIndex++}`,
1491
1577
  tool: 'query_customer_db',
1492
1578
  input: {
1493
- sql: statement,
1579
+ sql,
1494
1580
  ...(options?.maxRows !== undefined
1495
1581
  ? { max_rows: options.maxRows }
1496
1582
  : {}),
@@ -1681,6 +1767,14 @@ export class PlayContextImpl {
1681
1767
  );
1682
1768
  }
1683
1769
 
1770
+ private get currentAuthoringContractEdition(): PlayAuthoringContractEdition {
1771
+ return (
1772
+ this.activeInlineComposition?.authoringContractEdition ??
1773
+ this.#options.authoringContractEdition ??
1774
+ PLAY_AUTHORING_CONTRACT_EDITION
1775
+ );
1776
+ }
1777
+
1684
1778
  private recordPlayCallStep(input: {
1685
1779
  playId: string;
1686
1780
  execution?: 'inline';
@@ -2276,23 +2370,16 @@ export class PlayContextImpl {
2276
2370
  if (!this.#options.heartbeatRuntimeStepReceipts) {
2277
2371
  return await this.assertRuntimeToolReceiptOwnership(requests);
2278
2372
  }
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
- );
2373
+ const uniqueTargets = [
2374
+ ...new Map(targets.map((target) => [target.receiptKey, target])).values(),
2375
+ ];
2376
+ const receipts = await this.#options.heartbeatRuntimeStepReceipts({
2377
+ runId: this.currentReceiptOwnerRunId,
2378
+ runAttempt: this.currentRunAttempt,
2379
+ keys: uniqueTargets.map((target) => target.receiptKey),
2380
+ leaseIds: uniqueTargets.map((target) => target.leaseId),
2381
+ });
2382
+ this.assertRuntimeToolReceiptHeartbeatResults(uniqueTargets, receipts);
2296
2383
  }
2297
2384
 
2298
2385
  private async flushRuntimeToolOwnershipAssertionBatch(): Promise<void> {
@@ -2303,26 +2390,24 @@ export class PlayContextImpl {
2303
2390
 
2304
2391
  if (this.#options.heartbeatRuntimeStepReceipts) {
2305
2392
  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
- }
2393
+ const uniqueTargets = [
2394
+ ...new Map(
2395
+ targets.map((target) => [target.receiptKey, target]),
2396
+ ).values(),
2397
+ ];
2398
+ const receipts = await this.#options.heartbeatRuntimeStepReceipts({
2399
+ runId: this.currentReceiptOwnerRunId,
2400
+ runAttempt: this.currentRunAttempt,
2401
+ keys: uniqueTargets.map((target) => target.receiptKey),
2402
+ leaseIds: uniqueTargets.map((target) => target.leaseId),
2403
+ });
2312
2404
  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
- );
2405
+ for (let index = 0; index < uniqueTargets.length; index += 1) {
2406
+ renewed.set(
2407
+ uniqueTargets[index]!.receiptKey,
2408
+ receipts[index] ?? null,
2409
+ );
2410
+ }
2326
2411
  for (const assertion of assertions) {
2327
2412
  const lost = assertion.targets.find((target) => {
2328
2413
  const receipt = renewed.get(target.receiptKey);
@@ -2385,21 +2470,21 @@ export class PlayContextImpl {
2385
2470
  for (const assertion of assertions) assertion.reject(error);
2386
2471
  }
2387
2472
 
2388
- private assertRuntimeToolReceiptHeartbeatResult(
2389
- keys: string[],
2390
- leaseId: string,
2473
+ private assertRuntimeToolReceiptHeartbeatResults(
2474
+ targets: Array<{ receiptKey: string; leaseId: string }>,
2391
2475
  receipts: Array<RuntimeStepReceipt | null>,
2392
2476
  ): void {
2393
- for (let index = 0; index < keys.length; index += 1) {
2477
+ for (let index = 0; index < targets.length; index += 1) {
2478
+ const target = targets[index]!;
2394
2479
  const receipt = this.normalizeRuntimeStepReceipt(
2395
- keys[index] ?? '',
2480
+ target.receiptKey,
2396
2481
  receipts[index],
2397
2482
  );
2398
- if (!this.runtimeToolReceiptStillOwned(receipt, leaseId)) {
2483
+ if (!this.runtimeToolReceiptStillOwned(receipt, target.leaseId)) {
2399
2484
  throw new RuntimeReceiptLeaseLostError({
2400
- receiptKey: keys[index] ?? 'unknown',
2485
+ receiptKey: target.receiptKey,
2401
2486
  runId: this.currentReceiptOwnerRunId,
2402
- leaseId,
2487
+ leaseId: target.leaseId,
2403
2488
  });
2404
2489
  }
2405
2490
  }
@@ -3146,6 +3231,14 @@ export class PlayContextImpl {
3146
3231
  execute: (context: { leaseId: string | null }) => Promise<T>;
3147
3232
  },
3148
3233
  ): Promise<T> {
3234
+ const stalePolicy = resolveDurableCallCachePolicy(
3235
+ opts.staleAfterSeconds,
3236
+ operation === 'step'
3237
+ ? 'ctx.step.staleAfterSeconds'
3238
+ : operation === 'fetch'
3239
+ ? 'ctx.fetch.staleAfterSeconds'
3240
+ : 'ctx.tools.execute.staleAfterSeconds',
3241
+ );
3149
3242
  const receiptKey =
3150
3243
  opts.receiptKey?.trim() ||
3151
3244
  durableCtxKey({
@@ -3154,7 +3247,7 @@ export class PlayContextImpl {
3154
3247
  operation,
3155
3248
  id,
3156
3249
  semanticKey: opts.semanticKey,
3157
- staleAfterSeconds: opts.staleAfterSeconds,
3250
+ staleAfterSeconds: stalePolicy.staleAfterSeconds,
3158
3251
  });
3159
3252
  return await executeWithDurableRuntimeReceipt<T>({
3160
3253
  operation,
@@ -3162,7 +3255,7 @@ export class PlayContextImpl {
3162
3255
  runId: this.currentReceiptOwnerRunId,
3163
3256
  receiptKey,
3164
3257
  store: this.durableReceiptExecutionStore(),
3165
- force: opts.force,
3258
+ force: opts.force === true || stalePolicy.forceRefresh,
3166
3259
  repairRunningReceiptForSameRun: opts.repairRunningReceiptForSameRun,
3167
3260
  repairRunningReceiptForSameRunAfterWaitTimeout:
3168
3261
  opts.repairRunningReceiptForSameRunAfterWaitTimeout,
@@ -3492,15 +3585,20 @@ export class PlayContextImpl {
3492
3585
  forceFailedRefresh: boolean;
3493
3586
  staleAfterSeconds?: number | null;
3494
3587
  } {
3588
+ const stalePolicy = resolveDurableCallCachePolicy(
3589
+ options?.staleAfterSeconds,
3590
+ );
3495
3591
  return {
3496
3592
  force:
3497
3593
  options?.force === true ||
3594
+ stalePolicy.forceRefresh ||
3498
3595
  this.#options.cachePolicy?.forceToolRefresh === true,
3499
3596
  forceFailedRefresh:
3500
3597
  options?.force === true ||
3598
+ stalePolicy.forceRefresh ||
3501
3599
  this.#options.cachePolicy?.forceToolRefresh === true ||
3502
3600
  this.#options.cachePolicy?.forceFailedToolRefresh === true,
3503
- staleAfterSeconds: options?.staleAfterSeconds ?? null,
3601
+ staleAfterSeconds: stalePolicy.staleAfterSeconds,
3504
3602
  };
3505
3603
  }
3506
3604
 
@@ -4033,10 +4131,19 @@ export class PlayContextImpl {
4033
4131
 
4034
4132
  async csv(
4035
4133
  path: string,
4036
- _options?: CsvOptions,
4134
+ options?: CsvOptions,
4037
4135
  ): Promise<PlayDataset<Record<string, unknown>>> {
4038
4136
  this.assertInlineChildContract('dataset_child');
4039
- void _options;
4137
+ if (options) {
4138
+ for (const [field, value] of [
4139
+ ['ctx.csv.options.description', options.description],
4140
+ ['ctx.csv.options.columns', options.columns],
4141
+ ['ctx.csv.options.rename', options.rename],
4142
+ ['ctx.csv.options.required', options.required],
4143
+ ] as const) {
4144
+ validateOptionalPlayAuthoringField(field, value);
4145
+ }
4146
+ }
4040
4147
  // In cloud mode, CSV data is passed in — path is just a label
4041
4148
  // The activity loads the actual data before creating the ctx
4042
4149
  throw new Error(
@@ -4053,7 +4160,7 @@ export class PlayContextImpl {
4053
4160
  key: string,
4054
4161
  items: PlayDatasetInput<T>,
4055
4162
  input: RuntimeStepProgram,
4056
- options?: DatasetOptions<T>,
4163
+ options?: RuntimeDatasetOptions<T>,
4057
4164
  ): Promise<PlayDataset<Record<string, unknown>>>;
4058
4165
  dataset<
4059
4166
  T extends Record<string, unknown>,
@@ -4062,7 +4169,7 @@ export class PlayContextImpl {
4062
4169
  key: string,
4063
4170
  items: PlayDatasetInput<T>,
4064
4171
  input?: MapFieldDefinition<T, TColumns> | RuntimeStepProgram,
4065
- options?: DatasetOptions<T>,
4172
+ options?: RuntimeDatasetOptions<T>,
4066
4173
  ): RuntimeDatasetBuilder<T> | Promise<PlayDataset<Record<string, unknown>>> {
4067
4174
  this.assertInlineChildContract('dataset_child');
4068
4175
  if (rowContext.getStore()) {
@@ -4091,7 +4198,7 @@ export class PlayContextImpl {
4091
4198
  key: string,
4092
4199
  items: PlayDatasetInput<T>,
4093
4200
  input: RuntimeStepProgram,
4094
- options?: DatasetOptions<T>,
4201
+ options?: RuntimeDatasetOptions<T>,
4095
4202
  ): never;
4096
4203
  map<T extends Record<string, unknown>>(
4097
4204
  _key: string,
@@ -4099,7 +4206,7 @@ export class PlayContextImpl {
4099
4206
  _input?:
4100
4207
  | MapFieldDefinition<T, Record<string, unknown>>
4101
4208
  | RuntimeStepProgram,
4102
- _options?: DatasetOptions<T>,
4209
+ _options?: RuntimeDatasetOptions<T>,
4103
4210
  ): never {
4104
4211
  void _key;
4105
4212
  void _items;
@@ -4112,7 +4219,7 @@ export class PlayContextImpl {
4112
4219
  key: string,
4113
4220
  items: PlayDatasetInput<T>,
4114
4221
  program: RuntimeStepProgram,
4115
- options?: DatasetOptions<T>,
4222
+ options?: RuntimeDatasetOptions<T>,
4116
4223
  ): Promise<PlayDataset<Record<string, unknown>>> {
4117
4224
  const definition = this.stepProgramToMapDefinition(program);
4118
4225
  return this.runMapDefinition(key, items, definition, options);
@@ -4126,7 +4233,11 @@ export class PlayContextImpl {
4126
4233
  if (!isRuntimeStepProgram(program)) {
4127
4234
  throw new Error('ctx.runSteps(program, input) requires steps().');
4128
4235
  }
4129
- if (options?.description) {
4236
+ if (options?.description !== undefined) {
4237
+ validatePlayAuthoringField(
4238
+ 'ctx.runSteps.options.description',
4239
+ options.description,
4240
+ );
4130
4241
  this.log(options.description);
4131
4242
  }
4132
4243
  return (await this.executeStepProgram(program, input, 0, [], {
@@ -4141,13 +4252,28 @@ export class PlayContextImpl {
4141
4252
  key: string,
4142
4253
  items: PlayDatasetInput<T>,
4143
4254
  input: MapFieldDefinition<T, TColumns>,
4144
- options?: DatasetOptions<T>,
4255
+ options?: RuntimeDatasetOptions<T>,
4145
4256
  ): Promise<PlayDataset<Record<string, unknown>>> {
4146
4257
  if (rowContext.getStore()) {
4147
4258
  throw new Error(
4148
4259
  'Nested ctx.dataset() is not supported. Flatten your columns into one dataset, or keep custom per-row logic inside a single column.',
4149
4260
  );
4150
4261
  }
4262
+ validatePlayAuthoringField('ctx.dataset.key', key);
4263
+ if (options) {
4264
+ validateOptionalPlayAuthoringField(
4265
+ 'ctx.dataset.run.description',
4266
+ options.description,
4267
+ );
4268
+ validateOptionalPlayAuthoringField(
4269
+ 'ctx.dataset.run.onRowError',
4270
+ options.onRowError,
4271
+ );
4272
+ validateOptionalPlayAuthoringField('ctx.dataset.run.mode', options.mode);
4273
+ if (options.key !== undefined) {
4274
+ validatePlayAuthoringField('ctx.dataset.run.key', options.key);
4275
+ }
4276
+ }
4151
4277
  const normalizedMapKey = this.normalizeContextKey(key, 'map');
4152
4278
 
4153
4279
  const normalizedMapNamespace = normalizeTableNamespace(normalizedMapKey);
@@ -6955,6 +7081,33 @@ export class PlayContextImpl {
6955
7081
  input: Record<string, unknown>,
6956
7082
  options?: ToolCallOptions,
6957
7083
  ): Promise<unknown> {
7084
+ if (options?.description !== undefined) {
7085
+ validatePlayAuthoringField(
7086
+ 'ctx.tools.execute.description',
7087
+ options.description,
7088
+ );
7089
+ }
7090
+ if (options?.force !== undefined) {
7091
+ validatePlayAuthoringField('ctx.tools.execute.force', options.force);
7092
+ }
7093
+ if (
7094
+ options?.timeoutMs !== undefined &&
7095
+ this.currentAuthoringContractEdition >= 2
7096
+ ) {
7097
+ validatePlayAuthoringField(
7098
+ 'ctx.tools.execute.timeoutMs',
7099
+ options.timeoutMs,
7100
+ );
7101
+ }
7102
+ if (
7103
+ options?.receiptWaitMs !== undefined &&
7104
+ this.currentAuthoringContractEdition >= 2
7105
+ ) {
7106
+ validatePlayAuthoringField(
7107
+ 'ctx.tools.execute.receiptWaitMs',
7108
+ options.receiptWaitMs,
7109
+ );
7110
+ }
6958
7111
  const executionScope = this.currentExecutionScope;
6959
7112
  const normalizedKey = this.normalizeContextKey(key, 'tool');
6960
7113
  const toolCachePolicy = this.effectiveToolCallCachePolicy(options);
@@ -7061,7 +7214,11 @@ export class PlayContextImpl {
7061
7214
  logicalCallId,
7062
7215
  })
7063
7216
  : physicalDirectKey,
7064
- timeoutMs: resolveToolRuntimeTimeoutMs(toolId, options?.timeoutMs),
7217
+ timeoutMs: resolveToolRuntimeTimeoutMs(
7218
+ toolId,
7219
+ options?.timeoutMs,
7220
+ this.currentAuthoringContractEdition,
7221
+ ),
7065
7222
  ...(directReceiptLeaseId &&
7066
7223
  (this.#options.heartbeatRuntimeStepReceipts ||
7067
7224
  this.#options.getRuntimeStepReceipt ||
@@ -7163,6 +7320,7 @@ export class PlayContextImpl {
7163
7320
  const timeoutMs = resolveToolRuntimeTimeoutMs(
7164
7321
  toolId,
7165
7322
  options?.timeoutMs,
7323
+ this.currentAuthoringContractEdition,
7166
7324
  );
7167
7325
  this.enqueueToolCall({
7168
7326
  callId,
@@ -7177,7 +7335,7 @@ export class PlayContextImpl {
7177
7335
  toolId,
7178
7336
  input,
7179
7337
  ...(timeoutMs !== undefined ? { timeoutMs } : {}),
7180
- ...(typeof options?.receiptWaitMs === 'number'
7338
+ ...(options?.receiptWaitMs !== undefined
7181
7339
  ? { receiptWaitMs: options.receiptWaitMs }
7182
7340
  : {}),
7183
7341
  tableNamespace: store.tableNamespace,
@@ -7214,8 +7372,11 @@ export class PlayContextImpl {
7214
7372
  toolRetryPolicy?.requiresExecutionFence === true,
7215
7373
  executionLockTtlMs: Math.min(
7216
7374
  600_000,
7217
- resolveToolRuntimeTimeoutMs(toolId, options?.timeoutMs) ??
7218
- 300_000,
7375
+ resolveToolRuntimeTimeoutMs(
7376
+ toolId,
7377
+ options?.timeoutMs,
7378
+ this.currentAuthoringContractEdition,
7379
+ ) ?? 300_000,
7219
7380
  ),
7220
7381
  staleAfterSeconds: toolCachePolicy.staleAfterSeconds,
7221
7382
  onClaimedResult: (output, receiptKey) =>
@@ -7237,7 +7398,7 @@ export class PlayContextImpl {
7237
7398
  execute: ({ leaseId }) => executeTool({ leaseId }),
7238
7399
  runningReceiptWaitMaxAttempts:
7239
7400
  resolveRuntimeToolReceiptWaitMaxAttempts(
7240
- typeof options?.receiptWaitMs === 'number'
7401
+ options?.receiptWaitMs !== undefined
7241
7402
  ? { max_wait_ms: options.receiptWaitMs }
7242
7403
  : input,
7243
7404
  ),
@@ -7391,8 +7552,6 @@ export class PlayContextImpl {
7391
7552
  input: Record<string, unknown>,
7392
7553
  options?: PlayCallOptions,
7393
7554
  ): Promise<TOutput> {
7394
- assertNoSecretTaint(input, 'ctx.runPlay input');
7395
- const normalizedKey = this.normalizeContextKey(key, 'runPlay');
7396
7555
  if (
7397
7556
  arguments.length === 3 &&
7398
7557
  typeof playRef === 'object' &&
@@ -7405,13 +7564,26 @@ export class PlayContextImpl {
7405
7564
  const resolvedName =
7406
7565
  typeof playRef === 'string'
7407
7566
  ? playRef
7408
- : typeof playRef.playName === 'string'
7567
+ : playRef && typeof playRef.playName === 'string'
7409
7568
  ? playRef.playName
7410
- : (playRef.name ?? '');
7569
+ : playRef && typeof playRef.name === 'string'
7570
+ ? playRef.name
7571
+ : '';
7411
7572
 
7412
7573
  if (!resolvedName.trim()) {
7413
7574
  throw new Error('ctx.runPlay(...) requires a resolvable play name.');
7414
7575
  }
7576
+ validatePlayAuthoringField('ctx.runPlay.playRef', playRef);
7577
+ validatePlayAuthoringField('ctx.runPlay.input', input);
7578
+ assertNoSecretTaint(input, 'ctx.runPlay input');
7579
+ validatePlayAuthoringField('ctx.runPlay.key', key);
7580
+ if (options?.description !== undefined) {
7581
+ validatePlayAuthoringField(
7582
+ 'ctx.runPlay.options.description',
7583
+ options.description,
7584
+ );
7585
+ }
7586
+ const normalizedKey = this.normalizeContextKey(key, 'runPlay');
7415
7587
  if (!this.#options.resolvePlay) {
7416
7588
  throw new Error(
7417
7589
  'ctx.runPlay(...) is unavailable because no play resolver was configured.',
@@ -7423,11 +7595,13 @@ export class PlayContextImpl {
7423
7595
  `Unable to resolve play "${resolvedName}" for ctx.runPlay(...).`,
7424
7596
  );
7425
7597
  }
7426
- const childToolErrorSchemaVersion = normalizePlayContractCompatibility(
7598
+ const childCompatibility = normalizePlayContractCompatibility(
7427
7599
  resolvedPlay.contractSnapshot?.compatibility ??
7428
7600
  resolvedPlay.artifact?.compatibility ??
7429
7601
  buildPlayContractCompatibility(),
7430
- ).toolErrorSchemaVersion;
7602
+ );
7603
+ const childToolErrorSchemaVersion =
7604
+ childCompatibility.toolErrorSchemaVersion;
7431
7605
  const childExecutionDecision = resolveChildExecutionStrategy({
7432
7606
  pipeline: resolvedPlay.staticPipeline,
7433
7607
  timeoutMs: options?.timeoutMs,
@@ -7499,6 +7673,8 @@ export class PlayContextImpl {
7499
7673
  playName: resolvedName,
7500
7674
  staticPipeline: resolvedPlay.staticPipeline ?? null,
7501
7675
  toolErrorSchemaVersion: childToolErrorSchemaVersion,
7676
+ authoringContractEdition:
7677
+ childCompatibility.authoringContractEdition,
7502
7678
  },
7503
7679
  () => this.executeResolvedPlay(resolvedPlay, this, input),
7504
7680
  );
@@ -7680,8 +7856,11 @@ export class PlayContextImpl {
7680
7856
 
7681
7857
  async sleep(ms: number): Promise<void> {
7682
7858
  this.assertInlineChildContract('suspending_child');
7859
+ const delayMs =
7860
+ this.currentAuthoringContractEdition >= 2
7861
+ ? (validatePlayAuthoringField('ctx.sleep.ms', ms), ms)
7862
+ : Math.max(0, Math.round(ms));
7683
7863
  if (this.#options.durableBoundaries) {
7684
- const delayMs = Math.max(0, Math.round(ms));
7685
7864
  const boundaryId = this.durableBoundaryId(
7686
7865
  `sleep-${this.sleepBoundaryIndex}-${delayMs}`,
7687
7866
  );
@@ -7704,7 +7883,7 @@ export class PlayContextImpl {
7704
7883
  delayMs,
7705
7884
  });
7706
7885
  }
7707
- return new Promise((resolve) => setTimeout(resolve, ms));
7886
+ return new Promise((resolve) => setTimeout(resolve, delayMs));
7708
7887
  }
7709
7888
 
7710
7889
  async fetch(
@@ -7713,6 +7892,7 @@ export class PlayContextImpl {
7713
7892
  init: SecretAwareRequestInit = {},
7714
7893
  options?: FetchOptions,
7715
7894
  ): Promise<PlayFetchResponse> {
7895
+ validatePlayAuthoringField('ctx.fetch.key', key);
7716
7896
  const normalizedKey = this.normalizeContextKey(key, 'fetch');
7717
7897
  const rowStore = rowContext.getStore();
7718
7898
  const rowFetchScope = rowStore
@@ -7899,8 +8079,13 @@ export class PlayContextImpl {
7899
8079
  async step<T>(
7900
8080
  key: string,
7901
8081
  run: () => T | Promise<T>,
7902
- options?: StepOptions,
8082
+ options?: RuntimeStepOptions,
7903
8083
  ): Promise<T> {
8084
+ validatePlayAuthoringField('ctx.step.id', key);
8085
+ validateOptionalPlayAuthoringField(
8086
+ 'ctx.step.semanticKey',
8087
+ options?.semanticKey,
8088
+ );
7904
8089
  const normalizedKey = this.normalizeContextKey(key, 'step');
7905
8090
  if (!normalizedKey.trim()) {
7906
8091
  throw new Error('ctx.step(key, fn) requires a non-empty stable step id.');
@@ -8040,58 +8225,50 @@ export class PlayContextImpl {
8040
8225
  this.toolCallResolvers.has(request.callId),
8041
8226
  );
8042
8227
  if (unsettled.length === 0) return 'terminal';
8043
- const byLeaseId = new Map<string, Map<string, ToolCallRequest[]>>();
8228
+ const byReceiptKey = new Map<string, ToolCallRequest[]>();
8044
8229
  for (const request of unsettled) {
8045
- const leaseId = request.receiptLeaseId!;
8046
- const byReceiptKey = byLeaseId.get(leaseId) ?? new Map();
8047
8230
  const receiptKey = request.receiptKey!;
8048
8231
  const group = byReceiptKey.get(receiptKey) ?? [];
8049
8232
  group.push(request);
8050
8233
  byReceiptKey.set(receiptKey, group);
8051
- byLeaseId.set(leaseId, byReceiptKey);
8052
8234
  }
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({
8235
+ // One content-addressed receipt can have many same-run followers. The
8236
+ // store renews unique receipt rows, not request positions, so dedupe by
8237
+ // key while preserving the matching per-row lease id.
8238
+ const entries = [...byReceiptKey];
8239
+ const keys = entries.map(([receiptKey]) => receiptKey);
8240
+ const leaseIds = entries.map(
8241
+ ([, requests]) => requests[0]!.receiptLeaseId!,
8242
+ );
8243
+ const receipts = await heartbeatReceipts({
8244
+ runId: this.currentReceiptOwnerRunId,
8245
+ runAttempt: this.currentRunAttempt,
8246
+ keys,
8247
+ leaseIds,
8248
+ });
8249
+ for (let index = 0; index < entries.length; index += 1) {
8250
+ const [receiptKey, requests] = entries[index]!;
8251
+ const leaseId = leaseIds[index]!;
8252
+ // Completion can race this bulk heartbeat response. A settled
8253
+ // receipt group no longer needs ownership and must not turn that
8254
+ // race into a false lease-loss failure.
8255
+ if (
8256
+ !requests.some((request) =>
8257
+ this.toolCallResolvers.has(request.callId),
8258
+ )
8259
+ ) {
8260
+ continue;
8261
+ }
8262
+ if (
8263
+ !this.runtimeToolReceiptStillOwned(receipts[index] ?? null, leaseId)
8264
+ ) {
8265
+ throw new RuntimeReceiptLeaseLostError({
8266
+ receiptKey,
8063
8267
  runId: this.currentReceiptOwnerRunId,
8064
- runAttempt: this.currentRunAttempt,
8065
8268
  leaseId,
8066
- keys,
8067
8269
  });
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
- );
8270
+ }
8271
+ }
8095
8272
  return 'active';
8096
8273
  },
8097
8274
  isLeaseLost: (error) => error instanceof RuntimeReceiptLeaseLostError,
@@ -9224,7 +9401,11 @@ export class PlayContextImpl {
9224
9401
  this.#options.executionGatewayBaseUrl?.trim() || this.#options.baseUrl;
9225
9402
  const url = `${executionBaseUrl.replace(/\/$/, '')}/api/v2/integrations/${encodeURIComponent(toolId)}/${executeSuffix}`;
9226
9403
  const toolErrorSchemaVersion = this.currentToolErrorSchemaVersion;
9227
- const timeoutMs = resolveToolRuntimeTimeoutMs(toolId, options?.timeoutMs);
9404
+ const timeoutMs = resolveToolRuntimeTimeoutMs(
9405
+ toolId,
9406
+ options?.timeoutMs,
9407
+ this.currentAuthoringContractEdition,
9408
+ );
9228
9409
  const provider = toolId.split(/[._]/)[0]?.trim() || 'provider';
9229
9410
  const activityId = `provider:${toolId}`;
9230
9411
  let retryActivityEmitted = false;