deepline 0.1.168 → 0.1.170

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 (32) hide show
  1. package/dist/bundling-sources/apps/play-runner-workers/src/coordinator-entry.ts +317 -26
  2. package/dist/bundling-sources/apps/play-runner-workers/src/dedup-do.ts +100 -8
  3. package/dist/bundling-sources/apps/play-runner-workers/src/entry.ts +294 -81
  4. package/dist/bundling-sources/apps/play-runner-workers/src/runtime/map-chunk-plan.ts +119 -33
  5. package/dist/bundling-sources/apps/play-runner-workers/src/runtime/receipts.ts +4 -1
  6. package/dist/bundling-sources/apps/play-runner-workers/src/runtime/tool-receipts.ts +56 -0
  7. package/dist/bundling-sources/apps/play-runner-workers/src/workflow-instance-create.ts +3 -0
  8. package/dist/bundling-sources/sdk/src/client.ts +29 -1
  9. package/dist/bundling-sources/sdk/src/play.ts +4 -0
  10. package/dist/bundling-sources/sdk/src/release.ts +2 -2
  11. package/dist/bundling-sources/sdk/src/types.ts +3 -0
  12. package/dist/bundling-sources/shared_libs/play-data-plane/column-names.ts +50 -8
  13. package/dist/bundling-sources/shared_libs/play-data-plane/sheet-contract.ts +40 -1
  14. package/dist/bundling-sources/shared_libs/play-runtime/app-runtime-api.ts +1 -0
  15. package/dist/bundling-sources/shared_libs/play-runtime/context.ts +135 -4
  16. package/dist/bundling-sources/shared_libs/play-runtime/ctx-types.ts +9 -3
  17. package/dist/bundling-sources/shared_libs/play-runtime/protocol.ts +1 -0
  18. package/dist/bundling-sources/shared_libs/play-runtime/runtime-api.ts +2 -0
  19. package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backend.ts +2 -0
  20. package/dist/bundling-sources/shared_libs/play-runtime/work-receipts.ts +1 -0
  21. package/dist/bundling-sources/shared_libs/plays/static-pipeline.ts +202 -45
  22. package/dist/cli/index.js +70 -113
  23. package/dist/cli/index.mjs +70 -113
  24. package/dist/{compiler-manifest-VhtM9n24.d.mts → compiler-manifest-OwORQ07f.d.mts} +1 -0
  25. package/dist/{compiler-manifest-VhtM9n24.d.ts → compiler-manifest-OwORQ07f.d.ts} +1 -0
  26. package/dist/index.d.mts +9 -1
  27. package/dist/index.d.ts +9 -1
  28. package/dist/index.js +26 -5
  29. package/dist/index.mjs +26 -5
  30. package/dist/plays/bundle-play-file.d.mts +2 -2
  31. package/dist/plays/bundle-play-file.d.ts +2 -2
  32. package/package.json +1 -1
@@ -14,9 +14,10 @@ import {
14
14
 
15
15
  export const CACHE_ENABLED_SIMPLE_MAP_CHUNK_SIZE = 10_000;
16
16
  export { TOOL_CALLING_MAP_CHUNK_SIZE };
17
- // Cloudflare preview Workers enforce the 50-subrequest invocation limit. Leave
18
- // headroom for coordinator/storage calls around row-level unbatched tool RPCs.
19
- export const UNBATCHED_TOOL_SUBREQUESTS_PER_CHUNK_BUDGET = 40;
17
+ // Paid Cloudflare Workers support a much higher configured subrequest limit.
18
+ // Keep a large buffer for coordinator/storage calls around row-level unbatched
19
+ // tool RPCs, but avoid pathological one-row chunks for provider waterfalls.
20
+ export const UNBATCHED_TOOL_SUBREQUESTS_PER_CHUNK_BUDGET = 500;
20
21
  // Fresh unbatched tool calls use one RUNTIME_API integration execute RPC and
21
22
  // one HARNESS durable-receipt completion RPC. Batch-cap rows by both.
22
23
  export const SUBREQUESTS_PER_UNBATCHED_TOOL_CALL = 2;
@@ -46,6 +47,10 @@ type ChunkSizingPlan = {
46
47
 
47
48
  type MapToolStats = [totalToolCount: number, unbatchedToolCount: number];
48
49
 
50
+ function fieldRoot(field: string | null | undefined): string {
51
+ return String(field ?? '').split('.', 1)[0] ?? '';
52
+ }
53
+
49
54
  function declarationBelongsToMapOutput(
50
55
  declaration: { field?: string | null },
51
56
  outputFields: readonly string[],
@@ -76,39 +81,124 @@ function countToolSubsteps(
76
81
  let unbatchedToolCount = 0;
77
82
 
78
83
  for (const substep of substeps) {
79
- if (substep.type === 'tool') {
84
+ const [nestedTotal, nestedUnbatched] = countToolSubstep(substep);
85
+ totalToolCount += nestedTotal;
86
+ unbatchedToolCount += nestedUnbatched;
87
+ }
88
+
89
+ return [totalToolCount, unbatchedToolCount];
90
+ }
91
+
92
+ function countToolSubstep(substep: PlayStaticSubstep): MapToolStats {
93
+ if (substep.type === 'tool') {
94
+ return [1, isUnbatchedTool(substep.toolId) ? 1 : 0];
95
+ }
96
+
97
+ if (substep.type === 'waterfall') {
98
+ let totalToolCount = 0;
99
+ let unbatchedToolCount = 0;
100
+ for (const step of substep.steps ?? []) {
101
+ if (!step.toolId) continue;
80
102
  totalToolCount += 1;
81
- if (isUnbatchedTool(substep.toolId)) {
103
+ if (isUnbatchedTool(step.toolId)) {
82
104
  unbatchedToolCount += 1;
83
105
  }
84
- continue;
85
106
  }
107
+ return [totalToolCount, unbatchedToolCount];
108
+ }
86
109
 
87
- if (substep.type === 'waterfall') {
88
- for (const step of substep.steps ?? []) {
89
- if (!step.toolId) continue;
90
- totalToolCount += 1;
91
- if (isUnbatchedTool(step.toolId)) {
92
- unbatchedToolCount += 1;
93
- }
94
- }
95
- continue;
96
- }
110
+ if (substep.type === 'step_suite') {
111
+ return countStepSuiteTools(substep);
112
+ }
97
113
 
98
- if (
99
- substep.type === 'step_suite' ||
100
- substep.type === 'control_flow' ||
101
- substep.type === 'dataset'
102
- ) {
103
- const [nestedTotal, nestedUnbatched] = countToolSubsteps(
104
- substep.steps ?? [],
114
+ if (substep.type === 'control_flow') {
115
+ if (substep.kind === 'conditional' && substep.branches?.length) {
116
+ const branchStats = substep.branches.map((branch) =>
117
+ countToolSubsteps(branch.steps),
118
+ );
119
+ return branchStats.reduce<MapToolStats>(
120
+ ([totalMax, unbatchedMax], [total, unbatched]) => [
121
+ Math.max(totalMax, total),
122
+ Math.max(unbatchedMax, unbatched),
123
+ ],
124
+ [0, 0],
105
125
  );
106
- totalToolCount += nestedTotal;
107
- unbatchedToolCount += nestedUnbatched;
108
126
  }
127
+ return countToolSubsteps(substep.steps);
109
128
  }
110
129
 
111
- return [totalToolCount, unbatchedToolCount];
130
+ if (substep.type === 'dataset') {
131
+ return countToolSubsteps(substep.steps ?? []);
132
+ }
133
+
134
+ return [0, 0];
135
+ }
136
+
137
+ function countStepSuiteTools(
138
+ substep: Extract<PlayStaticSubstep, { type: 'step_suite' }>,
139
+ ): MapToolStats {
140
+ const childStats = substep.steps.map((step) => ({
141
+ step,
142
+ stats: countToolSubstep(step),
143
+ }));
144
+ const conditionalChildren = childStats.filter(
145
+ ({ step }) =>
146
+ step.conditional ||
147
+ (step.type === 'control_flow' && step.kind === 'conditional'),
148
+ );
149
+ if (
150
+ conditionalChildren.length === 0 ||
151
+ conditionalChildren.length < childStats.length / 2
152
+ ) {
153
+ return childStats.reduce<MapToolStats>(
154
+ ([totalSum, unbatchedSum], { stats: [total, unbatched] }) => [
155
+ totalSum + total,
156
+ unbatchedSum + unbatched,
157
+ ],
158
+ [0, 0],
159
+ );
160
+ }
161
+
162
+ const suiteRoot = fieldRoot(substep.field);
163
+ const sameRoot = childStats.every(({ step }) => {
164
+ if (!('field' in step)) return true;
165
+ return fieldRoot(step.field) === suiteRoot;
166
+ });
167
+ if (!sameRoot) {
168
+ return childStats.reduce<MapToolStats>(
169
+ ([totalSum, unbatchedSum], { stats: [total, unbatched] }) => [
170
+ totalSum + total,
171
+ unbatchedSum + unbatched,
172
+ ],
173
+ [0, 0],
174
+ );
175
+ }
176
+
177
+ const unconditionalStats = childStats
178
+ .filter(
179
+ ({ step }) =>
180
+ !step.conditional &&
181
+ !(step.type === 'control_flow' && step.kind === 'conditional'),
182
+ )
183
+ .reduce<MapToolStats>(
184
+ ([totalSum, unbatchedSum], { stats: [total, unbatched] }) => [
185
+ totalSum + total,
186
+ unbatchedSum + unbatched,
187
+ ],
188
+ [0, 0],
189
+ );
190
+ const conditionalStats = conditionalChildren.reduce<MapToolStats>(
191
+ ([totalSum, unbatchedSum], { stats: [total, unbatched] }) => [
192
+ totalSum + total,
193
+ unbatchedSum + unbatched,
194
+ ],
195
+ [0, 0],
196
+ );
197
+
198
+ return [
199
+ unconditionalStats[0] + conditionalStats[0],
200
+ unconditionalStats[1] + conditionalStats[1],
201
+ ];
112
202
  }
113
203
 
114
204
  function countProducerTools(
@@ -125,13 +215,9 @@ function countProducerTools(
125
215
  }
126
216
  }
127
217
  if (producer.substep.type === 'waterfall') {
128
- for (const step of producer.substep.steps ?? []) {
129
- if (!step.toolId) continue;
130
- totalToolCount += 1;
131
- if (isUnbatchedTool(step.toolId)) {
132
- unbatchedToolCount += 1;
133
- }
134
- }
218
+ const [nestedTotal, nestedUnbatched] = countToolSubstep(producer.substep);
219
+ totalToolCount += nestedTotal;
220
+ unbatchedToolCount += nestedUnbatched;
135
221
  }
136
222
  if (producer.steps?.length) {
137
223
  const [nestedTotal, nestedUnbatched] = countProducerTools(producer.steps);
@@ -27,8 +27,11 @@ type RuntimeReceiptContext = {
27
27
  receiptStore: WorkerRuntimeReceiptStore;
28
28
  };
29
29
 
30
- const WORKER_RECEIPT_WAIT_MAX_ATTEMPTS = 240;
30
+ const WORKER_RECEIPT_DEFAULT_WAIT_MS = 300_000;
31
31
  const WORKER_RECEIPT_WAIT_DELAY_MS = 250;
32
+ const WORKER_RECEIPT_WAIT_MAX_ATTEMPTS = Math.ceil(
33
+ WORKER_RECEIPT_DEFAULT_WAIT_MS / WORKER_RECEIPT_WAIT_DELAY_MS,
34
+ );
32
35
 
33
36
  class RuntimeReceiptWaitTimeoutError extends Error {
34
37
  constructor(key: string) {
@@ -22,6 +22,8 @@ export type WorkerToolReceiptGroupPlan<TRequest> = {
22
22
  export function canReclaimTimedOutWorkerToolReceipt(input: {
23
23
  ownerRunId?: string | null;
24
24
  currentRunId: string;
25
+ updatedAt?: string | null;
26
+ nowMs?: number;
25
27
  }): boolean {
26
28
  const currentRunId = input.currentRunId.trim();
27
29
  return Boolean(currentRunId);
@@ -93,6 +95,60 @@ export function planWorkerToolReceiptGroups<TRequest>(
93
95
  };
94
96
  }
95
97
 
98
+ export function resolveWorkerToolReceiptGroupWaitMaxAttempts<TRequest>(
99
+ requests: TRequest[],
100
+ getWaitMaxAttempts: (request: TRequest) => number,
101
+ ): number {
102
+ return requests.reduce((maxAttempts, request) => {
103
+ const attempts = getWaitMaxAttempts(request);
104
+ return Number.isFinite(attempts) && attempts > maxAttempts
105
+ ? attempts
106
+ : maxAttempts;
107
+ }, 1);
108
+ }
109
+
110
+ export type WorkerToolRuntimeTimeoutRequest = {
111
+ input: Record<string, unknown>;
112
+ runtimeTimeoutMs?: number;
113
+ };
114
+
115
+ export type WorkerToolRuntimeTimeoutClaim = {
116
+ request: WorkerToolRuntimeTimeoutRequest;
117
+ followers: WorkerToolRuntimeTimeoutRequest[];
118
+ };
119
+
120
+ export function resolveWorkerToolRuntimeTimeoutMs(
121
+ claimedRequests: WorkerToolRuntimeTimeoutClaim[],
122
+ input: {
123
+ resolveOwnerTimeoutMs: (
124
+ request: WorkerToolRuntimeTimeoutRequest,
125
+ ) => number | undefined;
126
+ },
127
+ ): number | undefined {
128
+ let timeoutMs = 0;
129
+ for (const claimed of claimedRequests) {
130
+ const ownerTimeoutMs = input.resolveOwnerTimeoutMs(claimed.request);
131
+ if (
132
+ typeof ownerTimeoutMs === 'number' &&
133
+ Number.isFinite(ownerTimeoutMs) &&
134
+ ownerTimeoutMs > timeoutMs
135
+ ) {
136
+ timeoutMs = ownerTimeoutMs;
137
+ }
138
+ for (const follower of claimed.followers) {
139
+ const followerTimeoutMs = follower.runtimeTimeoutMs;
140
+ if (
141
+ typeof followerTimeoutMs === 'number' &&
142
+ Number.isFinite(followerTimeoutMs) &&
143
+ followerTimeoutMs > timeoutMs
144
+ ) {
145
+ timeoutMs = followerTimeoutMs;
146
+ }
147
+ }
148
+ }
149
+ return timeoutMs > 0 ? timeoutMs : undefined;
150
+ }
151
+
96
152
  export function markWorkerToolReceiptResultCached(
97
153
  value: unknown,
98
154
  cacheKey: string,
@@ -19,11 +19,13 @@ export async function createOrAttachWorkflowInstance<
19
19
  >(input: {
20
20
  create: () => Promise<T>;
21
21
  getExisting: () => Promise<T>;
22
+ onCreateFailure?: (error: unknown) => Promise<void>;
22
23
  }): Promise<WorkflowInstanceCreateResult<T>> {
23
24
  try {
24
25
  return { instance: await input.create(), startMode: 'created' };
25
26
  } catch (error) {
26
27
  if (!isWorkflowInstanceAlreadyExistsError(error)) {
28
+ await input.onCreateFailure?.(error);
27
29
  throw error;
28
30
  }
29
31
  try {
@@ -32,6 +34,7 @@ export async function createOrAttachWorkflowInstance<
32
34
  startMode: 'reattached_existing',
33
35
  };
34
36
  } catch (attachError) {
37
+ await input.onCreateFailure?.(attachError);
35
38
  const message =
36
39
  attachError instanceof Error
37
40
  ? attachError.message
@@ -91,6 +91,23 @@ const REGISTER_PLAY_ARTIFACTS_COMPILE_CONCURRENCY = 3;
91
91
  const REGISTER_PLAY_ARTIFACTS_MAX_BATCH_COUNT = 3;
92
92
  const REGISTER_PLAY_ARTIFACTS_MAX_BATCH_BYTES = 2_500_000;
93
93
 
94
+ function normalizePlayRunIntegrationMode(
95
+ value: unknown,
96
+ ): 'live' | 'eval_stub' | 'fixture' | undefined {
97
+ if (value === 'live' || value === 'eval_stub' || value === 'fixture') {
98
+ return value;
99
+ }
100
+ return undefined;
101
+ }
102
+
103
+ function resolvePlayRunIntegrationMode(
104
+ request: StartPlayRunRequest,
105
+ ): 'live' | 'eval_stub' | 'fixture' | undefined {
106
+ return normalizePlayRunIntegrationMode(
107
+ request.integrationMode ?? process.env.DEEPLINE_EVAL_INTEGRATION_MODE,
108
+ );
109
+ }
110
+
94
111
  function sleep(ms: number): Promise<void> {
95
112
  return new Promise((resolve) => setTimeout(resolve, ms));
96
113
  }
@@ -1238,6 +1255,7 @@ export class DeeplineClient {
1238
1255
  * ```
1239
1256
  */
1240
1257
  async startPlayRun(request: StartPlayRunRequest): Promise<PlayRunStart> {
1258
+ const integrationMode = resolvePlayRunIntegrationMode(request);
1241
1259
  const response = await this.http.post<Record<string, unknown>>(
1242
1260
  '/api/v2/plays/run',
1243
1261
  {
@@ -1279,6 +1297,7 @@ export class DeeplineClient {
1279
1297
  // defaults to workers_edge; tests and runtime probes that want a
1280
1298
  // different profile pass `request.profile` explicitly.
1281
1299
  ...(request.profile ? { profile: request.profile } : {}),
1300
+ ...(integrationMode ? { integrationMode } : {}),
1282
1301
  },
1283
1302
  );
1284
1303
  return normalizePlayRunStart(response);
@@ -1299,6 +1318,7 @@ export class DeeplineClient {
1299
1318
  request: StartPlayRunRequest,
1300
1319
  options?: { signal?: AbortSignal },
1301
1320
  ): AsyncGenerator<PlayLiveEvent> {
1321
+ const integrationMode = resolvePlayRunIntegrationMode(request);
1302
1322
  const body = {
1303
1323
  ...(request.name ? { name: request.name } : {}),
1304
1324
  ...(request.revisionId ? { revisionId: request.revisionId } : {}),
@@ -1335,6 +1355,7 @@ export class DeeplineClient {
1335
1355
  ? { waitForCompletionMs: request.waitForCompletionMs }
1336
1356
  : {}),
1337
1357
  ...(request.profile ? { profile: request.profile } : {}),
1358
+ ...(integrationMode ? { integrationMode } : {}),
1338
1359
  };
1339
1360
  for await (const event of this.http.streamSse<PlayLiveEvent>(
1340
1361
  '/api/v2/plays/run?stream=true',
@@ -1538,8 +1559,15 @@ export class DeeplineClient {
1538
1559
  sourceFiles?: Record<string, string>;
1539
1560
  description?: string;
1540
1561
  artifact: Record<string, unknown>;
1562
+ integrationMode?: 'live' | 'eval_stub' | 'fixture';
1541
1563
  }): Promise<PlayCheckResult> {
1542
- return this.http.post('/api/v2/plays/check', input);
1564
+ const integrationMode = normalizePlayRunIntegrationMode(
1565
+ input.integrationMode ?? process.env.DEEPLINE_EVAL_INTEGRATION_MODE,
1566
+ );
1567
+ return this.http.post('/api/v2/plays/check', {
1568
+ ...input,
1569
+ ...(integrationMode ? { integrationMode } : {}),
1570
+ });
1543
1571
  }
1544
1572
 
1545
1573
  /**
@@ -300,6 +300,10 @@ export type ToolExecutionRequest = {
300
300
  force?: boolean;
301
301
  /** Numeric TTL in seconds for this tool checkpoint. */
302
302
  staleAfterSeconds?: number;
303
+ /** Runtime transport timeout in milliseconds. This is not sent to the provider. */
304
+ timeoutMs?: number;
305
+ /** Follower wait budget in milliseconds before a running receipt is reclaimable. */
306
+ receiptWaitMs?: number;
303
307
  };
304
308
 
305
309
  export type StepResolver<Row, Value> = (
@@ -104,10 +104,10 @@ export const SDK_RELEASE = {
104
104
  // 0.1.111 ships dataset-native tool list getters and result row datasets.
105
105
  // 0.1.154 removes the short-lived generated enrich StepOptions recompute
106
106
  // fields shipped in 0.1.153.
107
- version: '0.1.168',
107
+ version: '0.1.170',
108
108
  apiContract: '2026-06-dataset-handle-results-hard-cutover',
109
109
  supportPolicy: {
110
- latest: '0.1.168',
110
+ latest: '0.1.170',
111
111
  minimumSupported: '0.1.53',
112
112
  deprecatedBelow: '0.1.53',
113
113
  commandMinimumSupported: [
@@ -919,6 +919,7 @@ export interface PlayRunStart {
919
919
  export interface PlayCheckResult {
920
920
  valid: boolean;
921
921
  errors: string[];
922
+ warnings?: string[];
922
923
  staticPipeline?: Record<string, unknown> | null;
923
924
  toolGetterHints?: PlayCheckToolGetterHint[];
924
925
  artifactHash?: string | null;
@@ -1005,6 +1006,8 @@ export interface StartPlayRunRequest {
1005
1006
  * should leave this unset.
1006
1007
  */
1007
1008
  profile?: string;
1009
+ /** Optional per-run provider execution mode for eval/smoke runs. */
1010
+ integrationMode?: 'live' | 'eval_stub' | 'fixture';
1008
1011
  }
1009
1012
 
1010
1013
  /**
@@ -1,11 +1,7 @@
1
- /**
2
- * SQL-safe physical column name for a play-authored logical field id.
3
- *
4
- * Runtime Sheet adapters must use this when they create or discover physical
5
- * JSONB columns so logical names such as `_metadata` and `person.email` do not
6
- * drift between writer and reader paths.
7
- */
8
- export function sqlSafePlayColumnName(id: string): string {
1
+ const POSTGRES_IDENTIFIER_MAX_BYTES = 63;
2
+ const HASH_SUFFIX_LENGTH = 10;
3
+
4
+ function normalizePlayColumnName(id: string): string {
9
5
  const normalized = id
10
6
  .trim()
11
7
  .replace(/\.+/g, '__')
@@ -15,3 +11,49 @@ export function sqlSafePlayColumnName(id: string): string {
15
11
  const safe = normalized || 'column';
16
12
  return /^[A-Za-z_]/.test(safe) ? safe : `c_${safe}`;
17
13
  }
14
+
15
+ function stableHexSuffix(value: string): string {
16
+ let primary = 0x811c9dc5;
17
+ let secondary = 0x811c9dc5 ^ value.length;
18
+ for (let index = 0; index < value.length; index += 1) {
19
+ const code = value.charCodeAt(index);
20
+ primary = Math.imul(primary ^ code, 0x01000193) >>> 0;
21
+ secondary = Math.imul(secondary ^ (code + index), 0x01000193) >>> 0;
22
+ }
23
+ return `${primary.toString(16).padStart(8, '0')}${secondary
24
+ .toString(16)
25
+ .padStart(8, '0')}`.slice(0, HASH_SUFFIX_LENGTH);
26
+ }
27
+
28
+ /**
29
+ * SQL-safe physical column name for a play-authored logical field id.
30
+ *
31
+ * Runtime Sheet adapters must use this when they create or discover physical
32
+ * JSONB columns so logical names such as `_metadata` and `person.email` do not
33
+ * drift between writer and reader paths.
34
+ */
35
+ export function sqlSafePlayColumnName(id: string): string {
36
+ const prefixed = normalizePlayColumnName(id);
37
+ if (prefixed.length <= POSTGRES_IDENTIFIER_MAX_BYTES) {
38
+ return prefixed;
39
+ }
40
+
41
+ const hash = stableHexSuffix(prefixed);
42
+ const maxPrefixLength =
43
+ POSTGRES_IDENTIFIER_MAX_BYTES - HASH_SUFFIX_LENGTH - 1;
44
+ const prefix = prefixed
45
+ .slice(0, maxPrefixLength)
46
+ .replace(/_+$/g, '')
47
+ .slice(0, maxPrefixLength);
48
+ return `${prefix}_${hash}`;
49
+ }
50
+
51
+ export function legacyPostgresTruncatedPlayColumnName(
52
+ id: string,
53
+ ): string | null {
54
+ const prefixed = normalizePlayColumnName(id);
55
+ if (prefixed.length <= POSTGRES_IDENTIFIER_MAX_BYTES) {
56
+ return null;
57
+ }
58
+ return prefixed.slice(0, POSTGRES_IDENTIFIER_MAX_BYTES);
59
+ }
@@ -1,11 +1,19 @@
1
1
  import type { PlaySheetContract } from '../plays/static-pipeline';
2
- import { sqlSafePlayColumnName } from './column-names';
2
+ import {
3
+ legacyPostgresTruncatedPlayColumnName,
4
+ sqlSafePlayColumnName,
5
+ } from './column-names';
3
6
 
4
7
  export type PhysicalSheetColumnProjection = {
5
8
  sqlName: string;
6
9
  fieldName: string;
7
10
  };
8
11
 
12
+ export type LegacyPhysicalSheetColumnBackfill = {
13
+ sqlName: string;
14
+ legacySqlName: string;
15
+ };
16
+
9
17
  const RUNTIME_SHEET_SYSTEM_FIELDS = new Set([
10
18
  '_key',
11
19
  '_status',
@@ -58,6 +66,37 @@ export function physicalSheetColumnNames(
58
66
  );
59
67
  }
60
68
 
69
+ export function legacyPhysicalSheetColumnBackfills(
70
+ sheetContract: PlaySheetContract | null | undefined,
71
+ ): LegacyPhysicalSheetColumnBackfill[] {
72
+ if (!sheetContract) {
73
+ return [];
74
+ }
75
+ const seen = new Set<string>();
76
+ const backfills: LegacyPhysicalSheetColumnBackfill[] = [];
77
+ for (const column of sheetContract.columns) {
78
+ const sqlName = column.sqlName.trim();
79
+ if (!sqlName || sqlName.startsWith('_')) {
80
+ continue;
81
+ }
82
+ const candidates = [
83
+ typeof column.field === 'string' ? column.field : null,
84
+ column.id,
85
+ ];
86
+ for (const candidate of candidates) {
87
+ if (!candidate) continue;
88
+ const legacySqlName = legacyPostgresTruncatedPlayColumnName(candidate);
89
+ const key = legacySqlName ? `${sqlName}:${legacySqlName}` : '';
90
+ if (!legacySqlName || legacySqlName === sqlName || seen.has(key)) {
91
+ continue;
92
+ }
93
+ seen.add(key);
94
+ backfills.push({ sqlName, legacySqlName });
95
+ }
96
+ }
97
+ return backfills;
98
+ }
99
+
61
100
  export function physicalSheetColumnFieldLookup(
62
101
  sheetContract: PlaySheetContract | null | undefined,
63
102
  ): Map<string, string> {
@@ -230,6 +230,7 @@ type RuntimeApiRequest =
230
230
  export type WorkerRuntimeApiContext = {
231
231
  baseUrl: string;
232
232
  executorToken: string;
233
+ integrationMode?: 'live' | 'eval_stub' | 'fixture' | null;
233
234
  vercelProtectionBypassToken?: string | null;
234
235
  fetch?: typeof fetch;
235
236
  };