deepline 0.1.62 → 0.1.64

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 (27) hide show
  1. package/dist/cli/index.js +702 -339
  2. package/dist/cli/index.mjs +727 -348
  3. package/dist/index.d.mts +7 -1
  4. package/dist/index.d.ts +7 -1
  5. package/dist/index.js +77 -51
  6. package/dist/index.mjs +77 -51
  7. package/dist/repo/apps/play-runner-workers/src/coordinator-entry.ts +9 -10
  8. package/dist/repo/apps/play-runner-workers/src/entry.ts +55 -0
  9. package/dist/repo/apps/play-runner-workers/src/runtime/dataset-handles.ts +36 -27
  10. package/dist/repo/apps/play-runner-workers/src/runtime/tool-http-errors.ts +5 -2
  11. package/dist/repo/sdk/src/client.ts +71 -63
  12. package/dist/repo/sdk/src/errors.ts +5 -1
  13. package/dist/repo/sdk/src/http.ts +25 -17
  14. package/dist/repo/sdk/src/plays/local-file-discovery.ts +93 -24
  15. package/dist/repo/sdk/src/release.ts +2 -2
  16. package/dist/repo/sdk/src/tool-output.ts +40 -20
  17. package/dist/repo/shared_libs/play-runtime/batch-runtime.ts +10 -3
  18. package/dist/repo/shared_libs/play-runtime/batching-types.ts +15 -4
  19. package/dist/repo/shared_libs/play-runtime/coordinator-headers.ts +2 -1
  20. package/dist/repo/shared_libs/play-runtime/dedup-backend.ts +0 -0
  21. package/dist/repo/shared_libs/play-runtime/default-batch-strategies.ts +3 -4
  22. package/dist/repo/shared_libs/play-runtime/run-failure.ts +1 -3
  23. package/dist/repo/shared_libs/play-runtime/step-lifecycle-tracker.ts +4 -1
  24. package/dist/repo/shared_libs/play-runtime/tool-batch-executor.ts +4 -1
  25. package/dist/repo/shared_libs/play-runtime/tool-result.ts +28 -15
  26. package/dist/repo/shared_libs/plays/dataset.ts +10 -11
  27. package/package.json +1 -1
package/dist/index.d.mts CHANGED
@@ -1206,7 +1206,7 @@ declare class DeeplineClient {
1206
1206
  * artifactStorageKey: 'plays/v1/orgs/acme/plays/my-play/artifacts/playgraph_abc123.json',
1207
1207
  * });
1208
1208
  * ```
1209
- */
1209
+ */
1210
1210
  startPlayRun(request: StartPlayRunRequest): Promise<PlayRunStart>;
1211
1211
  startPlayRunStream(request: StartPlayRunRequest, options?: {
1212
1212
  signal?: AbortSignal;
@@ -1881,6 +1881,12 @@ type ToolResultExtractorDescriptor = {
1881
1881
  paths: readonly string[];
1882
1882
  transforms?: readonly string[];
1883
1883
  enum?: readonly string[];
1884
+ overrides?: readonly ToolResultExtractorOverride[];
1885
+ };
1886
+ type ToolResultExtractorOverride = {
1887
+ paths: readonly string[];
1888
+ equals?: string | number | boolean | null;
1889
+ value: string | number | boolean | null;
1884
1890
  };
1885
1891
  type ToolResultTargetAccessor<T = unknown> = ToolResultTargetMetadata & {
1886
1892
  get(): T | null;
package/dist/index.d.ts CHANGED
@@ -1206,7 +1206,7 @@ declare class DeeplineClient {
1206
1206
  * artifactStorageKey: 'plays/v1/orgs/acme/plays/my-play/artifacts/playgraph_abc123.json',
1207
1207
  * });
1208
1208
  * ```
1209
- */
1209
+ */
1210
1210
  startPlayRun(request: StartPlayRunRequest): Promise<PlayRunStart>;
1211
1211
  startPlayRunStream(request: StartPlayRunRequest, options?: {
1212
1212
  signal?: AbortSignal;
@@ -1881,6 +1881,12 @@ type ToolResultExtractorDescriptor = {
1881
1881
  paths: readonly string[];
1882
1882
  transforms?: readonly string[];
1883
1883
  enum?: readonly string[];
1884
+ overrides?: readonly ToolResultExtractorOverride[];
1885
+ };
1886
+ type ToolResultExtractorOverride = {
1887
+ paths: readonly string[];
1888
+ equals?: string | number | boolean | null;
1889
+ value: string | number | boolean | null;
1884
1890
  };
1885
1891
  type ToolResultTargetAccessor<T = unknown> = ToolResultTargetMetadata & {
1886
1892
  get(): T | null;
package/dist/index.js CHANGED
@@ -88,7 +88,11 @@ var RateLimitError = class extends DeeplineError {
88
88
  /** Milliseconds to wait before retrying, from the `Retry-After` response header. Defaults to 5000. */
89
89
  retryAfterMs;
90
90
  constructor(retryAfterMs = 5e3, message) {
91
- super(message ?? `Rate limited. Retry after ${retryAfterMs}ms.`, 429, "RATE_LIMIT");
91
+ super(
92
+ message ?? `Rate limited. Retry after ${retryAfterMs}ms.`,
93
+ 429,
94
+ "RATE_LIMIT"
95
+ );
92
96
  this.name = "RateLimitError";
93
97
  this.retryAfterMs = retryAfterMs;
94
98
  }
@@ -232,10 +236,10 @@ function resolveConfig(options) {
232
236
 
233
237
  // src/release.ts
234
238
  var SDK_RELEASE = {
235
- version: "0.1.62",
239
+ version: "0.1.64",
236
240
  apiContract: "2026-05-play-bootstrap-dataset-summary",
237
241
  supportPolicy: {
238
- latest: "0.1.62",
242
+ latest: "0.1.64",
239
243
  minimumSupported: "0.1.53",
240
244
  deprecatedBelow: "0.1.53"
241
245
  }
@@ -258,7 +262,7 @@ var HttpClient = class {
258
262
  config;
259
263
  authHeaders(extra) {
260
264
  const headers = {
261
- "Authorization": `Bearer ${this.config.apiKey}`,
265
+ Authorization: `Bearer ${this.config.apiKey}`,
262
266
  "User-Agent": `deepline-ts-sdk/${SDK_VERSION}`,
263
267
  "X-Deepline-SDK-Version": SDK_VERSION,
264
268
  "X-Deepline-API-Contract": SDK_API_CONTRACT,
@@ -941,31 +945,34 @@ var DeeplineClient = class {
941
945
  * artifactStorageKey: 'plays/v1/orgs/acme/plays/my-play/artifacts/playgraph_abc123.json',
942
946
  * });
943
947
  * ```
944
- */
948
+ */
945
949
  async startPlayRun(request) {
946
- const response = await this.http.post("/api/v2/plays/run", {
947
- ...request.name ? { name: request.name } : {},
948
- ...request.revisionId ? { revisionId: request.revisionId } : {},
949
- ...request.artifactStorageKey ? { artifactStorageKey: request.artifactStorageKey } : {},
950
- ...request.sourceCode ? { sourceCode: request.sourceCode } : {},
951
- ...request.sourceFiles ? { sourceFiles: request.sourceFiles } : {},
952
- ..."staticPipeline" in request ? { staticPipeline: request.staticPipeline } : {},
953
- ...request.artifactHash ? { artifactHash: request.artifactHash } : {},
954
- ...request.graphHash ? { graphHash: request.graphHash } : {},
955
- ...request.runtimeArtifact ? { runtimeArtifact: request.runtimeArtifact } : {},
956
- ...request.compilerManifest ? { compilerManifest: request.compilerManifest } : {},
957
- ...request.inputFileUpload ? { inputFileUpload: request.inputFileUpload } : {},
958
- ...request.packagedFileUploads?.length ? { packagedFileUploads: request.packagedFileUploads } : {},
959
- ...request.input ? { input: request.input } : {},
960
- ...request.inputFile ? { inputFile: request.inputFile } : {},
961
- ...request.packagedFiles?.length ? { packagedFiles: request.packagedFiles } : {},
962
- ...request.force ? { force: true } : {},
963
- ...typeof request.waitForCompletionMs === "number" ? { waitForCompletionMs: request.waitForCompletionMs } : {},
964
- // Profile selection is the API's job, not the CLI's. The server
965
- // hardcodes workers_edge as the default; tests that want a
966
- // different profile pass `request.profile` explicitly.
967
- ...request.profile ? { profile: request.profile } : {}
968
- });
950
+ const response = await this.http.post(
951
+ "/api/v2/plays/run",
952
+ {
953
+ ...request.name ? { name: request.name } : {},
954
+ ...request.revisionId ? { revisionId: request.revisionId } : {},
955
+ ...request.artifactStorageKey ? { artifactStorageKey: request.artifactStorageKey } : {},
956
+ ...request.sourceCode ? { sourceCode: request.sourceCode } : {},
957
+ ...request.sourceFiles ? { sourceFiles: request.sourceFiles } : {},
958
+ ..."staticPipeline" in request ? { staticPipeline: request.staticPipeline } : {},
959
+ ...request.artifactHash ? { artifactHash: request.artifactHash } : {},
960
+ ...request.graphHash ? { graphHash: request.graphHash } : {},
961
+ ...request.runtimeArtifact ? { runtimeArtifact: request.runtimeArtifact } : {},
962
+ ...request.compilerManifest ? { compilerManifest: request.compilerManifest } : {},
963
+ ...request.inputFileUpload ? { inputFileUpload: request.inputFileUpload } : {},
964
+ ...request.packagedFileUploads?.length ? { packagedFileUploads: request.packagedFileUploads } : {},
965
+ ...request.input ? { input: request.input } : {},
966
+ ...request.inputFile ? { inputFile: request.inputFile } : {},
967
+ ...request.packagedFiles?.length ? { packagedFiles: request.packagedFiles } : {},
968
+ ...request.force ? { force: true } : {},
969
+ ...typeof request.waitForCompletionMs === "number" ? { waitForCompletionMs: request.waitForCompletionMs } : {},
970
+ // Profile selection is the API's job, not the CLI's. The server
971
+ // hardcodes workers_edge as the default; tests that want a
972
+ // different profile pass `request.profile` explicitly.
973
+ ...request.profile ? { profile: request.profile } : {}
974
+ }
975
+ );
969
976
  return normalizePlayRunStart(response);
970
977
  }
971
978
  async *startPlayRunStream(request, options) {
@@ -1217,10 +1224,7 @@ var DeeplineClient = class {
1217
1224
  }
1218
1225
  return formData;
1219
1226
  };
1220
- const response = await this.http.postFormData(
1221
- "/api/v2/plays/files/stage",
1222
- buildFormData
1223
- );
1227
+ const response = await this.http.postFormData("/api/v2/plays/files/stage", buildFormData);
1224
1228
  return response.files;
1225
1229
  }
1226
1230
  async resolveStagedPlayFiles(files) {
@@ -1667,7 +1671,9 @@ var DeeplineClient = class {
1667
1671
  }
1668
1672
  options?.onProgress?.(status);
1669
1673
  if (TERMINAL_PLAY_STATUSES.has(status.status)) {
1670
- const finalStatus = await this.getPlayStatus(status.runId || workflowId).catch(() => status);
1674
+ const finalStatus = await this.getPlayStatus(
1675
+ status.runId || workflowId
1676
+ ).catch(() => status);
1671
1677
  return playRunResultFromStatus(finalStatus, start, workflowId);
1672
1678
  }
1673
1679
  }
@@ -2073,9 +2079,7 @@ function deriveListKeys(input) {
2073
2079
  return keys;
2074
2080
  }
2075
2081
  const listPrefix = input.listPath.replace(/\[\d+\]$/, "");
2076
- for (const [target, paths] of Object.entries(
2077
- input.targetGetters ?? {}
2078
- )) {
2082
+ for (const [target, paths] of Object.entries(input.targetGetters ?? {})) {
2079
2083
  for (const rawPath of paths) {
2080
2084
  const path = String(rawPath || "").trim().replace(/^\./, "");
2081
2085
  if (!path) continue;
@@ -2121,21 +2125,20 @@ function buildTargets(result, extractors, targetGetters) {
2121
2125
  for (const [target, descriptor] of Object.entries(extractors ?? {})) {
2122
2126
  const fromExtractor = findFirstTargetByPath(result, descriptor.paths);
2123
2127
  if (!fromExtractor) continue;
2128
+ const transformed = coerceToEnum(
2129
+ applyExtractorTransforms(fromExtractor.value, descriptor),
2130
+ descriptor
2131
+ );
2132
+ const override = findExtractorOverride(result, descriptor);
2124
2133
  targets[target] = {
2125
2134
  path: fromExtractor.path,
2126
- value: coerceToEnum(
2127
- applyExtractorTransforms(fromExtractor.value, descriptor),
2128
- descriptor
2129
- )
2135
+ value: override?.value ?? transformed
2130
2136
  };
2131
2137
  }
2132
2138
  const metadataTargets = new Set(Object.keys(targetGetters ?? {}));
2133
2139
  for (const target of metadataTargets) {
2134
2140
  if (targets[target]) continue;
2135
- const fromMetadata = findFirstTargetByPath(
2136
- result,
2137
- targetGetters?.[target]
2138
- );
2141
+ const fromMetadata = findFirstTargetByPath(result, targetGetters?.[target]);
2139
2142
  if (fromMetadata) {
2140
2143
  targets[target] = fromMetadata;
2141
2144
  continue;
@@ -2152,6 +2155,19 @@ function buildTargets(result, extractors, targetGetters) {
2152
2155
  }
2153
2156
  return targets;
2154
2157
  }
2158
+ function findExtractorOverride(result, descriptor) {
2159
+ for (const override of descriptor.overrides ?? []) {
2160
+ const expected = Object.prototype.hasOwnProperty.call(override, "equals") ? override.equals : true;
2161
+ for (const path of override.paths) {
2162
+ const match = findFirstTargetByPath(result, [path]);
2163
+ if (!match) continue;
2164
+ if (match.value === expected) {
2165
+ return { value: override.value };
2166
+ }
2167
+ }
2168
+ }
2169
+ return null;
2170
+ }
2155
2171
  function buildLists(resolved, metadata) {
2156
2172
  const lists = {};
2157
2173
  for (const [name, list] of Object.entries(resolved)) {
@@ -2709,7 +2725,9 @@ function normalizeRows2(value) {
2709
2725
  });
2710
2726
  }
2711
2727
  function candidateRoots(payload) {
2712
- const roots = [{ path: null, value: payload }];
2728
+ const roots = [
2729
+ { path: null, value: payload }
2730
+ ];
2713
2731
  if (isPlainObject(payload) && isPlainObject(payload.toolResponse)) {
2714
2732
  roots.push({ path: "toolResponse", value: payload.toolResponse });
2715
2733
  if (Object.prototype.hasOwnProperty.call(payload.toolResponse, "raw")) {
@@ -2736,7 +2754,9 @@ function candidateRoots(payload) {
2736
2754
  function findBestArrayCandidate(value, pathPrefix = "", depth = 0) {
2737
2755
  if (depth > 5) return null;
2738
2756
  const directRows = normalizeRows2(value);
2739
- const hasObjectRow = directRows?.some((row) => Object.keys(row).some((key) => key !== "value")) ?? false;
2757
+ const hasObjectRow = directRows?.some(
2758
+ (row) => Object.keys(row).some((key) => key !== "value")
2759
+ ) ?? false;
2740
2760
  let best = directRows && directRows.length > 0 && hasObjectRow ? { path: pathPrefix, rows: directRows } : null;
2741
2761
  if (!isPlainObject(value)) {
2742
2762
  return best;
@@ -2752,7 +2772,9 @@ function findBestArrayCandidate(value, pathPrefix = "", depth = 0) {
2752
2772
  return best;
2753
2773
  }
2754
2774
  function tryConvertToList(payload, options) {
2755
- const listExtractorPaths = Array.isArray(options?.listExtractorPaths) ? options?.listExtractorPaths.filter((entry) => typeof entry === "string" && entry.trim().length > 0) : [];
2775
+ const listExtractorPaths = Array.isArray(options?.listExtractorPaths) ? options?.listExtractorPaths.filter(
2776
+ (entry) => typeof entry === "string" && entry.trim().length > 0
2777
+ ) : [];
2756
2778
  if (listExtractorPaths.length > 0) {
2757
2779
  for (const root of candidateRoots(payload)) {
2758
2780
  for (const extractorPath of listExtractorPaths) {
@@ -2818,7 +2840,9 @@ function writeCsvOutputFile(rows, stem) {
2818
2840
  const previewColumns = columns.slice(0, 5);
2819
2841
  const preview = [
2820
2842
  previewColumns.join(","),
2821
- ...previewRows.map((row) => previewColumns.map((column) => escapeCell(row[column])).join(","))
2843
+ ...previewRows.map(
2844
+ (row) => previewColumns.map((column) => escapeCell(row[column])).join(",")
2845
+ )
2822
2846
  ].join("\n");
2823
2847
  return {
2824
2848
  path: outputPath,
@@ -2831,9 +2855,11 @@ function extractSummaryFields(payload) {
2831
2855
  const candidates = candidateRoots(payload);
2832
2856
  for (const candidate of candidates) {
2833
2857
  if (!isPlainObject(candidate.value)) continue;
2834
- const summaryEntries = Object.entries(candidate.value).filter(([, value]) => {
2835
- return value == null || typeof value === "string" || typeof value === "number" || typeof value === "boolean";
2836
- });
2858
+ const summaryEntries = Object.entries(candidate.value).filter(
2859
+ ([, value]) => {
2860
+ return value == null || typeof value === "string" || typeof value === "number" || typeof value === "boolean";
2861
+ }
2862
+ );
2837
2863
  if (summaryEntries.length === 0) continue;
2838
2864
  return Object.fromEntries(summaryEntries);
2839
2865
  }
package/dist/index.mjs CHANGED
@@ -26,7 +26,11 @@ var RateLimitError = class extends DeeplineError {
26
26
  /** Milliseconds to wait before retrying, from the `Retry-After` response header. Defaults to 5000. */
27
27
  retryAfterMs;
28
28
  constructor(retryAfterMs = 5e3, message) {
29
- super(message ?? `Rate limited. Retry after ${retryAfterMs}ms.`, 429, "RATE_LIMIT");
29
+ super(
30
+ message ?? `Rate limited. Retry after ${retryAfterMs}ms.`,
31
+ 429,
32
+ "RATE_LIMIT"
33
+ );
30
34
  this.name = "RateLimitError";
31
35
  this.retryAfterMs = retryAfterMs;
32
36
  }
@@ -170,10 +174,10 @@ function resolveConfig(options) {
170
174
 
171
175
  // src/release.ts
172
176
  var SDK_RELEASE = {
173
- version: "0.1.62",
177
+ version: "0.1.64",
174
178
  apiContract: "2026-05-play-bootstrap-dataset-summary",
175
179
  supportPolicy: {
176
- latest: "0.1.62",
180
+ latest: "0.1.64",
177
181
  minimumSupported: "0.1.53",
178
182
  deprecatedBelow: "0.1.53"
179
183
  }
@@ -196,7 +200,7 @@ var HttpClient = class {
196
200
  config;
197
201
  authHeaders(extra) {
198
202
  const headers = {
199
- "Authorization": `Bearer ${this.config.apiKey}`,
203
+ Authorization: `Bearer ${this.config.apiKey}`,
200
204
  "User-Agent": `deepline-ts-sdk/${SDK_VERSION}`,
201
205
  "X-Deepline-SDK-Version": SDK_VERSION,
202
206
  "X-Deepline-API-Contract": SDK_API_CONTRACT,
@@ -879,31 +883,34 @@ var DeeplineClient = class {
879
883
  * artifactStorageKey: 'plays/v1/orgs/acme/plays/my-play/artifacts/playgraph_abc123.json',
880
884
  * });
881
885
  * ```
882
- */
886
+ */
883
887
  async startPlayRun(request) {
884
- const response = await this.http.post("/api/v2/plays/run", {
885
- ...request.name ? { name: request.name } : {},
886
- ...request.revisionId ? { revisionId: request.revisionId } : {},
887
- ...request.artifactStorageKey ? { artifactStorageKey: request.artifactStorageKey } : {},
888
- ...request.sourceCode ? { sourceCode: request.sourceCode } : {},
889
- ...request.sourceFiles ? { sourceFiles: request.sourceFiles } : {},
890
- ..."staticPipeline" in request ? { staticPipeline: request.staticPipeline } : {},
891
- ...request.artifactHash ? { artifactHash: request.artifactHash } : {},
892
- ...request.graphHash ? { graphHash: request.graphHash } : {},
893
- ...request.runtimeArtifact ? { runtimeArtifact: request.runtimeArtifact } : {},
894
- ...request.compilerManifest ? { compilerManifest: request.compilerManifest } : {},
895
- ...request.inputFileUpload ? { inputFileUpload: request.inputFileUpload } : {},
896
- ...request.packagedFileUploads?.length ? { packagedFileUploads: request.packagedFileUploads } : {},
897
- ...request.input ? { input: request.input } : {},
898
- ...request.inputFile ? { inputFile: request.inputFile } : {},
899
- ...request.packagedFiles?.length ? { packagedFiles: request.packagedFiles } : {},
900
- ...request.force ? { force: true } : {},
901
- ...typeof request.waitForCompletionMs === "number" ? { waitForCompletionMs: request.waitForCompletionMs } : {},
902
- // Profile selection is the API's job, not the CLI's. The server
903
- // hardcodes workers_edge as the default; tests that want a
904
- // different profile pass `request.profile` explicitly.
905
- ...request.profile ? { profile: request.profile } : {}
906
- });
888
+ const response = await this.http.post(
889
+ "/api/v2/plays/run",
890
+ {
891
+ ...request.name ? { name: request.name } : {},
892
+ ...request.revisionId ? { revisionId: request.revisionId } : {},
893
+ ...request.artifactStorageKey ? { artifactStorageKey: request.artifactStorageKey } : {},
894
+ ...request.sourceCode ? { sourceCode: request.sourceCode } : {},
895
+ ...request.sourceFiles ? { sourceFiles: request.sourceFiles } : {},
896
+ ..."staticPipeline" in request ? { staticPipeline: request.staticPipeline } : {},
897
+ ...request.artifactHash ? { artifactHash: request.artifactHash } : {},
898
+ ...request.graphHash ? { graphHash: request.graphHash } : {},
899
+ ...request.runtimeArtifact ? { runtimeArtifact: request.runtimeArtifact } : {},
900
+ ...request.compilerManifest ? { compilerManifest: request.compilerManifest } : {},
901
+ ...request.inputFileUpload ? { inputFileUpload: request.inputFileUpload } : {},
902
+ ...request.packagedFileUploads?.length ? { packagedFileUploads: request.packagedFileUploads } : {},
903
+ ...request.input ? { input: request.input } : {},
904
+ ...request.inputFile ? { inputFile: request.inputFile } : {},
905
+ ...request.packagedFiles?.length ? { packagedFiles: request.packagedFiles } : {},
906
+ ...request.force ? { force: true } : {},
907
+ ...typeof request.waitForCompletionMs === "number" ? { waitForCompletionMs: request.waitForCompletionMs } : {},
908
+ // Profile selection is the API's job, not the CLI's. The server
909
+ // hardcodes workers_edge as the default; tests that want a
910
+ // different profile pass `request.profile` explicitly.
911
+ ...request.profile ? { profile: request.profile } : {}
912
+ }
913
+ );
907
914
  return normalizePlayRunStart(response);
908
915
  }
909
916
  async *startPlayRunStream(request, options) {
@@ -1155,10 +1162,7 @@ var DeeplineClient = class {
1155
1162
  }
1156
1163
  return formData;
1157
1164
  };
1158
- const response = await this.http.postFormData(
1159
- "/api/v2/plays/files/stage",
1160
- buildFormData
1161
- );
1165
+ const response = await this.http.postFormData("/api/v2/plays/files/stage", buildFormData);
1162
1166
  return response.files;
1163
1167
  }
1164
1168
  async resolveStagedPlayFiles(files) {
@@ -1605,7 +1609,9 @@ var DeeplineClient = class {
1605
1609
  }
1606
1610
  options?.onProgress?.(status);
1607
1611
  if (TERMINAL_PLAY_STATUSES.has(status.status)) {
1608
- const finalStatus = await this.getPlayStatus(status.runId || workflowId).catch(() => status);
1612
+ const finalStatus = await this.getPlayStatus(
1613
+ status.runId || workflowId
1614
+ ).catch(() => status);
1609
1615
  return playRunResultFromStatus(finalStatus, start, workflowId);
1610
1616
  }
1611
1617
  }
@@ -2011,9 +2017,7 @@ function deriveListKeys(input) {
2011
2017
  return keys;
2012
2018
  }
2013
2019
  const listPrefix = input.listPath.replace(/\[\d+\]$/, "");
2014
- for (const [target, paths] of Object.entries(
2015
- input.targetGetters ?? {}
2016
- )) {
2020
+ for (const [target, paths] of Object.entries(input.targetGetters ?? {})) {
2017
2021
  for (const rawPath of paths) {
2018
2022
  const path = String(rawPath || "").trim().replace(/^\./, "");
2019
2023
  if (!path) continue;
@@ -2059,21 +2063,20 @@ function buildTargets(result, extractors, targetGetters) {
2059
2063
  for (const [target, descriptor] of Object.entries(extractors ?? {})) {
2060
2064
  const fromExtractor = findFirstTargetByPath(result, descriptor.paths);
2061
2065
  if (!fromExtractor) continue;
2066
+ const transformed = coerceToEnum(
2067
+ applyExtractorTransforms(fromExtractor.value, descriptor),
2068
+ descriptor
2069
+ );
2070
+ const override = findExtractorOverride(result, descriptor);
2062
2071
  targets[target] = {
2063
2072
  path: fromExtractor.path,
2064
- value: coerceToEnum(
2065
- applyExtractorTransforms(fromExtractor.value, descriptor),
2066
- descriptor
2067
- )
2073
+ value: override?.value ?? transformed
2068
2074
  };
2069
2075
  }
2070
2076
  const metadataTargets = new Set(Object.keys(targetGetters ?? {}));
2071
2077
  for (const target of metadataTargets) {
2072
2078
  if (targets[target]) continue;
2073
- const fromMetadata = findFirstTargetByPath(
2074
- result,
2075
- targetGetters?.[target]
2076
- );
2079
+ const fromMetadata = findFirstTargetByPath(result, targetGetters?.[target]);
2077
2080
  if (fromMetadata) {
2078
2081
  targets[target] = fromMetadata;
2079
2082
  continue;
@@ -2090,6 +2093,19 @@ function buildTargets(result, extractors, targetGetters) {
2090
2093
  }
2091
2094
  return targets;
2092
2095
  }
2096
+ function findExtractorOverride(result, descriptor) {
2097
+ for (const override of descriptor.overrides ?? []) {
2098
+ const expected = Object.prototype.hasOwnProperty.call(override, "equals") ? override.equals : true;
2099
+ for (const path of override.paths) {
2100
+ const match = findFirstTargetByPath(result, [path]);
2101
+ if (!match) continue;
2102
+ if (match.value === expected) {
2103
+ return { value: override.value };
2104
+ }
2105
+ }
2106
+ }
2107
+ return null;
2108
+ }
2093
2109
  function buildLists(resolved, metadata) {
2094
2110
  const lists = {};
2095
2111
  for (const [name, list] of Object.entries(resolved)) {
@@ -2647,7 +2663,9 @@ function normalizeRows2(value) {
2647
2663
  });
2648
2664
  }
2649
2665
  function candidateRoots(payload) {
2650
- const roots = [{ path: null, value: payload }];
2666
+ const roots = [
2667
+ { path: null, value: payload }
2668
+ ];
2651
2669
  if (isPlainObject(payload) && isPlainObject(payload.toolResponse)) {
2652
2670
  roots.push({ path: "toolResponse", value: payload.toolResponse });
2653
2671
  if (Object.prototype.hasOwnProperty.call(payload.toolResponse, "raw")) {
@@ -2674,7 +2692,9 @@ function candidateRoots(payload) {
2674
2692
  function findBestArrayCandidate(value, pathPrefix = "", depth = 0) {
2675
2693
  if (depth > 5) return null;
2676
2694
  const directRows = normalizeRows2(value);
2677
- const hasObjectRow = directRows?.some((row) => Object.keys(row).some((key) => key !== "value")) ?? false;
2695
+ const hasObjectRow = directRows?.some(
2696
+ (row) => Object.keys(row).some((key) => key !== "value")
2697
+ ) ?? false;
2678
2698
  let best = directRows && directRows.length > 0 && hasObjectRow ? { path: pathPrefix, rows: directRows } : null;
2679
2699
  if (!isPlainObject(value)) {
2680
2700
  return best;
@@ -2690,7 +2710,9 @@ function findBestArrayCandidate(value, pathPrefix = "", depth = 0) {
2690
2710
  return best;
2691
2711
  }
2692
2712
  function tryConvertToList(payload, options) {
2693
- const listExtractorPaths = Array.isArray(options?.listExtractorPaths) ? options?.listExtractorPaths.filter((entry) => typeof entry === "string" && entry.trim().length > 0) : [];
2713
+ const listExtractorPaths = Array.isArray(options?.listExtractorPaths) ? options?.listExtractorPaths.filter(
2714
+ (entry) => typeof entry === "string" && entry.trim().length > 0
2715
+ ) : [];
2694
2716
  if (listExtractorPaths.length > 0) {
2695
2717
  for (const root of candidateRoots(payload)) {
2696
2718
  for (const extractorPath of listExtractorPaths) {
@@ -2756,7 +2778,9 @@ function writeCsvOutputFile(rows, stem) {
2756
2778
  const previewColumns = columns.slice(0, 5);
2757
2779
  const preview = [
2758
2780
  previewColumns.join(","),
2759
- ...previewRows.map((row) => previewColumns.map((column) => escapeCell(row[column])).join(","))
2781
+ ...previewRows.map(
2782
+ (row) => previewColumns.map((column) => escapeCell(row[column])).join(",")
2783
+ )
2760
2784
  ].join("\n");
2761
2785
  return {
2762
2786
  path: outputPath,
@@ -2769,9 +2793,11 @@ function extractSummaryFields(payload) {
2769
2793
  const candidates = candidateRoots(payload);
2770
2794
  for (const candidate of candidates) {
2771
2795
  if (!isPlainObject(candidate.value)) continue;
2772
- const summaryEntries = Object.entries(candidate.value).filter(([, value]) => {
2773
- return value == null || typeof value === "string" || typeof value === "number" || typeof value === "boolean";
2774
- });
2796
+ const summaryEntries = Object.entries(candidate.value).filter(
2797
+ ([, value]) => {
2798
+ return value == null || typeof value === "string" || typeof value === "number" || typeof value === "boolean";
2799
+ }
2800
+ );
2775
2801
  if (summaryEntries.length === 0) continue;
2776
2802
  return Object.fromEntries(summaryEntries);
2777
2803
  }
@@ -991,12 +991,13 @@ async function persistWorkflowRetryState(input: {
991
991
  childPlayManifests: stripRetryChildManifestCode(
992
992
  input.params.childPlayManifests,
993
993
  ),
994
- packagedFiles: input.params.packagedFiles?.map((file) => ({
995
- playPath: file.playPath,
996
- storageKey: file.storageKey,
997
- contentType: file.contentType,
998
- bytes: file.bytes,
999
- })) ?? null,
994
+ packagedFiles:
995
+ input.params.packagedFiles?.map((file) => ({
996
+ playPath: file.playPath,
997
+ storageKey: file.storageKey,
998
+ contentType: file.contentType,
999
+ bytes: file.bytes,
1000
+ })) ?? null,
1000
1001
  };
1001
1002
  await callWorkflowPool<{ ok?: unknown }>(input.env, '/run-retry-state-put', {
1002
1003
  method: 'POST',
@@ -1115,9 +1116,7 @@ async function restartWorkflowAfterPlatformReset(input: {
1115
1116
  instanceId: retryInstance.id,
1116
1117
  started: true,
1117
1118
  });
1118
- input.ctx?.waitUntil(
1119
- input.oldInstance.terminate().catch(() => undefined),
1120
- );
1119
+ input.ctx?.waitUntil(input.oldInstance.terminate().catch(() => undefined));
1121
1120
  recordCoordinatorPerfTraceBuffered(input.env, input.ctx, {
1122
1121
  runId: input.runId,
1123
1122
  phase: 'coordinator.platform_deploy_retry',
@@ -3177,7 +3176,7 @@ async function handleWorkflowRoute(input: {
3177
3176
  error: error instanceof Error ? error.message : String(error),
3178
3177
  },
3179
3178
  });
3180
- });
3179
+ });
3181
3180
  input.ctx?.waitUntil(prewarmPromise);
3182
3181
  }
3183
3182
  await persistWorkflowRetryState({
@@ -1330,6 +1330,7 @@ function parseExtractorMetadata(
1330
1330
  if (paths.length === 0) return [];
1331
1331
  const transforms = parseStringArray(record.transforms);
1332
1332
  const enumValues = parseStringArray(record.enum);
1333
+ const overrides = parseExtractorOverrides(record.overrides);
1333
1334
  return [
1334
1335
  [
1335
1336
  key,
@@ -1337,6 +1338,7 @@ function parseExtractorMetadata(
1337
1338
  paths,
1338
1339
  ...(transforms.length > 0 ? { transforms } : {}),
1339
1340
  ...(enumValues.length > 0 ? { enum: enumValues } : {}),
1341
+ ...(overrides.length > 0 ? { overrides } : {}),
1340
1342
  },
1341
1343
  ],
1342
1344
  ] as const;
@@ -1345,6 +1347,39 @@ function parseExtractorMetadata(
1345
1347
  return entries.length > 0 ? Object.fromEntries(entries) : undefined;
1346
1348
  }
1347
1349
 
1350
+ function parseExtractorOverrides(
1351
+ value: unknown,
1352
+ ): NonNullable<
1353
+ NonNullable<ToolResultMetadataInput['extractors']>[string]['overrides']
1354
+ > {
1355
+ if (!Array.isArray(value)) return [];
1356
+ return value.flatMap((entry) => {
1357
+ if (!entry || typeof entry !== 'object' || Array.isArray(entry)) {
1358
+ return [];
1359
+ }
1360
+ const record = entry as Record<string, unknown>;
1361
+ const paths = parseStringArray(record.paths);
1362
+ if (paths.length === 0) return [];
1363
+ const equals =
1364
+ record.equals === null ||
1365
+ typeof record.equals === 'string' ||
1366
+ typeof record.equals === 'number' ||
1367
+ typeof record.equals === 'boolean'
1368
+ ? record.equals
1369
+ : true;
1370
+ const overrideValue = record.value;
1371
+ if (
1372
+ overrideValue !== null &&
1373
+ typeof overrideValue !== 'string' &&
1374
+ typeof overrideValue !== 'number' &&
1375
+ typeof overrideValue !== 'boolean'
1376
+ ) {
1377
+ return [];
1378
+ }
1379
+ return [{ paths, equals, value: overrideValue }];
1380
+ });
1381
+ }
1382
+
1348
1383
  function parseGetterMetadata(
1349
1384
  value: unknown,
1350
1385
  ): Record<string, readonly string[]> | undefined {
@@ -1370,8 +1405,23 @@ function syntheticToolMetadata(toolId: string): ToolResultMetadataInput {
1370
1405
  if (toolId === 'test_rate_limit') {
1371
1406
  return {
1372
1407
  toolId,
1408
+ extractors: {
1409
+ email_status: {
1410
+ paths: ['email_status'],
1411
+ transforms: ['emailStatus'],
1412
+ enum: ['valid', 'invalid', 'catch_all', 'unknown'],
1413
+ overrides: [
1414
+ {
1415
+ paths: ['mx_security_gateway'],
1416
+ equals: true,
1417
+ value: 'catch_all',
1418
+ },
1419
+ ],
1420
+ },
1421
+ },
1373
1422
  targetGetters: {
1374
1423
  email: ['email', 'value'],
1424
+ email_status: ['email_status'],
1375
1425
  },
1376
1426
  };
1377
1427
  }
@@ -1539,6 +1589,10 @@ function executeSyntheticTestRateLimit(
1539
1589
  : 'match'));
1540
1590
  const matched = syntheticMatchWindow(input, rowNumber);
1541
1591
  const matchedEmail = matched ? `${matchedPrefix}@${matchedDomain}` : null;
1592
+ const securityGateway =
1593
+ input.emit_security_gateway === true
1594
+ ? { email_status: 'valid', mx_security_gateway: true }
1595
+ : {};
1542
1596
  return {
1543
1597
  status: 'completed',
1544
1598
  key: String(input.key || ''),
@@ -1549,6 +1603,7 @@ function executeSyntheticTestRateLimit(
1549
1603
  email: matchedEmail,
1550
1604
  value: matchedEmail,
1551
1605
  batch: false,
1606
+ ...securityGateway,
1552
1607
  };
1553
1608
  }
1554
1609