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.
- package/dist/bundling-sources/apps/play-runner-workers/src/coordinator-entry.ts +317 -26
- package/dist/bundling-sources/apps/play-runner-workers/src/dedup-do.ts +100 -8
- package/dist/bundling-sources/apps/play-runner-workers/src/entry.ts +294 -81
- package/dist/bundling-sources/apps/play-runner-workers/src/runtime/map-chunk-plan.ts +119 -33
- package/dist/bundling-sources/apps/play-runner-workers/src/runtime/receipts.ts +4 -1
- package/dist/bundling-sources/apps/play-runner-workers/src/runtime/tool-receipts.ts +56 -0
- package/dist/bundling-sources/apps/play-runner-workers/src/workflow-instance-create.ts +3 -0
- package/dist/bundling-sources/sdk/src/client.ts +29 -1
- package/dist/bundling-sources/sdk/src/play.ts +4 -0
- package/dist/bundling-sources/sdk/src/release.ts +2 -2
- package/dist/bundling-sources/sdk/src/types.ts +3 -0
- package/dist/bundling-sources/shared_libs/play-data-plane/column-names.ts +50 -8
- package/dist/bundling-sources/shared_libs/play-data-plane/sheet-contract.ts +40 -1
- package/dist/bundling-sources/shared_libs/play-runtime/app-runtime-api.ts +1 -0
- package/dist/bundling-sources/shared_libs/play-runtime/context.ts +135 -4
- package/dist/bundling-sources/shared_libs/play-runtime/ctx-types.ts +9 -3
- package/dist/bundling-sources/shared_libs/play-runtime/protocol.ts +1 -0
- package/dist/bundling-sources/shared_libs/play-runtime/runtime-api.ts +2 -0
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backend.ts +2 -0
- package/dist/bundling-sources/shared_libs/play-runtime/work-receipts.ts +1 -0
- package/dist/bundling-sources/shared_libs/plays/static-pipeline.ts +202 -45
- package/dist/cli/index.js +70 -113
- package/dist/cli/index.mjs +70 -113
- package/dist/{compiler-manifest-VhtM9n24.d.mts → compiler-manifest-OwORQ07f.d.mts} +1 -0
- package/dist/{compiler-manifest-VhtM9n24.d.ts → compiler-manifest-OwORQ07f.d.ts} +1 -0
- package/dist/index.d.mts +9 -1
- package/dist/index.d.ts +9 -1
- package/dist/index.js +26 -5
- package/dist/index.mjs +26 -5
- package/dist/plays/bundle-play-file.d.mts +2 -2
- package/dist/plays/bundle-play-file.d.ts +2 -2
- 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
|
|
18
|
-
//
|
|
19
|
-
|
|
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
|
-
|
|
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(
|
|
103
|
+
if (isUnbatchedTool(step.toolId)) {
|
|
82
104
|
unbatchedToolCount += 1;
|
|
83
105
|
}
|
|
84
|
-
continue;
|
|
85
106
|
}
|
|
107
|
+
return [totalToolCount, unbatchedToolCount];
|
|
108
|
+
}
|
|
86
109
|
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
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
|
-
|
|
99
|
-
|
|
100
|
-
substep.
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
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
|
-
|
|
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
|
-
|
|
129
|
-
|
|
130
|
-
|
|
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
|
|
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
|
-
|
|
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.
|
|
107
|
+
version: '0.1.170',
|
|
108
108
|
apiContract: '2026-06-dataset-handle-results-hard-cutover',
|
|
109
109
|
supportPolicy: {
|
|
110
|
-
latest: '0.1.
|
|
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
|
-
|
|
3
|
-
|
|
4
|
-
|
|
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 {
|
|
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
|
};
|