deepline 0.1.181 → 0.1.183
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/child-play-await.ts +203 -6
- package/dist/bundling-sources/apps/play-runner-workers/src/child-play-submit.ts +8 -1
- package/dist/bundling-sources/apps/play-runner-workers/src/coordinator-entry.ts +633 -107
- package/dist/bundling-sources/apps/play-runner-workers/src/dedup-do.ts +3 -2
- package/dist/bundling-sources/apps/play-runner-workers/src/entry.ts +1932 -1838
- package/dist/bundling-sources/apps/play-runner-workers/src/runtime/harness-receipt-store.ts +116 -0
- package/dist/bundling-sources/apps/play-runner-workers/src/runtime/map-chunk-plan.ts +542 -78
- package/dist/bundling-sources/apps/play-runner-workers/src/runtime/output-datasets.ts +124 -12
- package/dist/bundling-sources/apps/play-runner-workers/src/runtime/receipts.ts +292 -11
- package/dist/bundling-sources/apps/play-runner-workers/src/runtime/run-work-dispatcher.ts +519 -0
- package/dist/bundling-sources/apps/play-runner-workers/src/runtime/tool-dispatch.ts +2381 -0
- package/dist/bundling-sources/apps/play-runner-workers/src/runtime/tool-receipts.ts +18 -5
- package/dist/bundling-sources/apps/play-runner-workers/src/runtime/work-budget.ts +130 -0
- package/dist/bundling-sources/apps/play-runner-workers/src/runtime/worker-platform-budget.ts +39 -0
- package/dist/bundling-sources/apps/play-runner-workers/src/workflow-retry-state.ts +2 -0
- package/dist/bundling-sources/sdk/src/client.ts +48 -5
- package/dist/bundling-sources/sdk/src/http.ts +23 -8
- package/dist/bundling-sources/sdk/src/play.ts +200 -26
- package/dist/bundling-sources/sdk/src/plays/harness-stub.ts +56 -3
- package/dist/bundling-sources/sdk/src/release.ts +2 -2
- package/dist/bundling-sources/sdk/src/types.ts +2 -0
- package/dist/bundling-sources/shared_libs/play-runtime/app-runtime-api.ts +132 -10
- package/dist/bundling-sources/shared_libs/play-runtime/auth-scope-resolver.ts +92 -0
- package/dist/bundling-sources/shared_libs/play-runtime/batch-runtime.ts +4 -4
- package/dist/bundling-sources/shared_libs/play-runtime/batching-types.ts +8 -0
- package/dist/bundling-sources/shared_libs/play-runtime/child-run-id.ts +101 -0
- package/dist/bundling-sources/shared_libs/play-runtime/context.ts +1857 -314
- package/dist/bundling-sources/shared_libs/play-runtime/ctx-types.ts +106 -6
- package/dist/bundling-sources/shared_libs/play-runtime/dedup-backend.ts +0 -0
- package/dist/bundling-sources/shared_libs/play-runtime/default-batch-strategies.ts +1 -0
- package/dist/bundling-sources/shared_libs/play-runtime/durability-store.ts +4 -0
- package/dist/bundling-sources/shared_libs/play-runtime/durable-call-cache.ts +116 -16
- package/dist/bundling-sources/shared_libs/play-runtime/durable-receipt-execution.ts +186 -20
- package/dist/bundling-sources/shared_libs/play-runtime/dynamic-worker-version.ts +2 -0
- package/dist/bundling-sources/shared_libs/play-runtime/execution-ledger-store.ts +133 -0
- package/dist/bundling-sources/shared_libs/play-runtime/governor/app-runtime-rate-state-backend.ts +245 -0
- package/dist/bundling-sources/shared_libs/play-runtime/governor/governor.ts +5 -5
- package/dist/bundling-sources/shared_libs/play-runtime/governor/policy.ts +14 -0
- package/dist/bundling-sources/shared_libs/play-runtime/lease-policy.ts +52 -0
- package/dist/bundling-sources/shared_libs/play-runtime/protocol.ts +41 -0
- package/dist/bundling-sources/shared_libs/play-runtime/providers.ts +3 -2
- package/dist/bundling-sources/shared_libs/play-runtime/query-result-dataset.ts +120 -0
- package/dist/bundling-sources/shared_libs/play-runtime/receipt-sql.ts +124 -0
- package/dist/bundling-sources/shared_libs/play-runtime/receipt-status.ts +6 -2
- package/dist/bundling-sources/shared_libs/play-runtime/row-isolation.ts +48 -2
- package/dist/bundling-sources/shared_libs/play-runtime/run-ledger.ts +158 -15
- package/dist/bundling-sources/shared_libs/play-runtime/run-terminal-source.ts +45 -0
- package/dist/bundling-sources/shared_libs/play-runtime/runtime-actions.ts +9 -0
- package/dist/bundling-sources/shared_libs/play-runtime/runtime-api.ts +2037 -262
- package/dist/bundling-sources/shared_libs/play-runtime/runtime-contract.ts +42 -0
- package/dist/bundling-sources/shared_libs/play-runtime/runtime-sheet-attempt-state-machine.ts +183 -0
- package/dist/bundling-sources/shared_libs/play-runtime/runtime-sheet-errors.ts +88 -0
- package/dist/bundling-sources/shared_libs/play-runtime/scheduler-backend.ts +8 -1
- package/dist/bundling-sources/shared_libs/play-runtime/sheet-attempt-sql.ts +103 -0
- package/dist/bundling-sources/shared_libs/play-runtime/suspension.ts +19 -1
- package/dist/bundling-sources/shared_libs/play-runtime/test-runtime-seams.ts +343 -0
- package/dist/bundling-sources/shared_libs/play-runtime/tool-execute-retry-policy.ts +43 -0
- package/dist/bundling-sources/shared_libs/play-runtime/tool-http-errors.ts +11 -0
- package/dist/bundling-sources/shared_libs/play-runtime/tool-result.ts +139 -17
- package/dist/bundling-sources/shared_libs/play-runtime/work-receipt-state-machine.ts +437 -0
- package/dist/bundling-sources/shared_libs/play-runtime/work-receipts.ts +83 -6
- package/dist/bundling-sources/shared_libs/security/safe-outbound-fetch.ts +24 -17
- package/dist/cli/index.js +85 -23
- package/dist/cli/index.mjs +85 -23
- package/dist/index.d.mts +4 -1
- package/dist/index.d.ts +4 -1
- package/dist/index.js +274 -35
- package/dist/index.mjs +274 -35
- package/dist/plays/bundle-play-file.mjs +2 -2
- package/package.json +1 -1
|
@@ -6,11 +6,13 @@ import {
|
|
|
6
6
|
export type WorkerToolReceiptSelection = {
|
|
7
7
|
claimableReceiptKey: string | null;
|
|
8
8
|
forceDurableRefresh: boolean;
|
|
9
|
+
forceFailedDurableRefresh: boolean;
|
|
9
10
|
};
|
|
10
11
|
|
|
11
12
|
export type WorkerToolReceiptGroup<TRequest> = {
|
|
12
13
|
claimableReceiptKey: string;
|
|
13
14
|
forceDurableRefresh: boolean;
|
|
15
|
+
forceFailedDurableRefresh: boolean;
|
|
14
16
|
requests: TRequest[];
|
|
15
17
|
};
|
|
16
18
|
|
|
@@ -155,6 +157,7 @@ export function selectWorkerToolReceipt(input: {
|
|
|
155
157
|
durableReceiptKey?: string | null;
|
|
156
158
|
localRetryCacheKey: string;
|
|
157
159
|
force: boolean;
|
|
160
|
+
forceFailed?: boolean;
|
|
158
161
|
allowLocalRetryReceipts: boolean;
|
|
159
162
|
}): WorkerToolReceiptSelection {
|
|
160
163
|
const durableReceiptKey = input.durableReceiptKey?.trim() || null;
|
|
@@ -165,6 +168,10 @@ export function selectWorkerToolReceipt(input: {
|
|
|
165
168
|
return {
|
|
166
169
|
claimableReceiptKey: durableReceiptKey ?? localRetryReceiptKey,
|
|
167
170
|
forceDurableRefresh: input.force === true && durableReceiptKey !== null,
|
|
171
|
+
forceFailedDurableRefresh:
|
|
172
|
+
input.force !== true &&
|
|
173
|
+
input.forceFailed === true &&
|
|
174
|
+
durableReceiptKey !== null,
|
|
168
175
|
};
|
|
169
176
|
}
|
|
170
177
|
|
|
@@ -176,17 +183,21 @@ export function planWorkerToolReceiptGroups<TRequest>(
|
|
|
176
183
|
durableReceiptKey?: string | null;
|
|
177
184
|
localRetryCacheKey: string;
|
|
178
185
|
force: boolean;
|
|
186
|
+
forceFailed?: boolean;
|
|
179
187
|
};
|
|
180
188
|
},
|
|
181
189
|
): WorkerToolReceiptGroupPlan<TRequest> {
|
|
182
190
|
const localRequests: TRequest[] = [];
|
|
183
191
|
const groupsByKey = new Map<string, WorkerToolReceiptGroup<TRequest>>();
|
|
184
192
|
for (const request of requests) {
|
|
185
|
-
const {
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
193
|
+
const {
|
|
194
|
+
claimableReceiptKey,
|
|
195
|
+
forceDurableRefresh,
|
|
196
|
+
forceFailedDurableRefresh,
|
|
197
|
+
} = selectWorkerToolReceipt({
|
|
198
|
+
...input.getReceiptInput(request),
|
|
199
|
+
allowLocalRetryReceipts: input.allowLocalRetryReceipts,
|
|
200
|
+
});
|
|
190
201
|
if (!claimableReceiptKey) {
|
|
191
202
|
localRequests.push(request);
|
|
192
203
|
continue;
|
|
@@ -194,10 +205,12 @@ export function planWorkerToolReceiptGroups<TRequest>(
|
|
|
194
205
|
const group = groupsByKey.get(claimableReceiptKey) ?? {
|
|
195
206
|
claimableReceiptKey,
|
|
196
207
|
forceDurableRefresh: false,
|
|
208
|
+
forceFailedDurableRefresh: false,
|
|
197
209
|
requests: [],
|
|
198
210
|
};
|
|
199
211
|
group.requests.push(request);
|
|
200
212
|
group.forceDurableRefresh ||= forceDurableRefresh;
|
|
213
|
+
group.forceFailedDurableRefresh ||= forceFailedDurableRefresh;
|
|
201
214
|
groupsByKey.set(claimableReceiptKey, group);
|
|
202
215
|
}
|
|
203
216
|
return {
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import { WORKER_PLATFORM_YIELD_LIMITS } from './worker-platform-budget';
|
|
2
|
+
|
|
3
|
+
export type WorkerWorkBudgetEventKind =
|
|
4
|
+
| 'tool'
|
|
5
|
+
| 'provider'
|
|
6
|
+
| 'receipt'
|
|
7
|
+
| 'heartbeat'
|
|
8
|
+
| 'sheet'
|
|
9
|
+
| 'log'
|
|
10
|
+
| 'egress';
|
|
11
|
+
|
|
12
|
+
export type WorkerWorkBudgetSnapshot = {
|
|
13
|
+
startedAt: number;
|
|
14
|
+
updatedAt: number;
|
|
15
|
+
elapsedMs: number;
|
|
16
|
+
counts: Record<WorkerWorkBudgetEventKind, number>;
|
|
17
|
+
elapsedMsByKind: Record<WorkerWorkBudgetEventKind, number>;
|
|
18
|
+
};
|
|
19
|
+
|
|
20
|
+
export type WorkerWorkBudgetYieldReason =
|
|
21
|
+
| 'elapsed'
|
|
22
|
+
| 'subrequest'
|
|
23
|
+
| 'provider'
|
|
24
|
+
| 'tool'
|
|
25
|
+
| 'receipt'
|
|
26
|
+
| 'heartbeat'
|
|
27
|
+
| 'sheet'
|
|
28
|
+
| 'log'
|
|
29
|
+
| 'egress';
|
|
30
|
+
|
|
31
|
+
export type WorkerWorkBudgetYieldDecision = {
|
|
32
|
+
shouldYield: boolean;
|
|
33
|
+
reason: WorkerWorkBudgetYieldReason | null;
|
|
34
|
+
snapshot: WorkerWorkBudgetSnapshot;
|
|
35
|
+
};
|
|
36
|
+
|
|
37
|
+
export type WorkerWorkBudgetYieldLimits = Partial<
|
|
38
|
+
Record<WorkerWorkBudgetYieldReason, number>
|
|
39
|
+
>;
|
|
40
|
+
|
|
41
|
+
export type WorkerWorkBudgetMeter = {
|
|
42
|
+
count(kind: WorkerWorkBudgetEventKind, amount?: number): void;
|
|
43
|
+
snapshot(): WorkerWorkBudgetSnapshot;
|
|
44
|
+
resetWindow(): void;
|
|
45
|
+
};
|
|
46
|
+
|
|
47
|
+
const WORK_BUDGET_EVENT_KINDS: WorkerWorkBudgetEventKind[] = [
|
|
48
|
+
'tool',
|
|
49
|
+
'provider',
|
|
50
|
+
'receipt',
|
|
51
|
+
'heartbeat',
|
|
52
|
+
'sheet',
|
|
53
|
+
'log',
|
|
54
|
+
'egress',
|
|
55
|
+
];
|
|
56
|
+
|
|
57
|
+
function zeroCounters(): Record<WorkerWorkBudgetEventKind, number> {
|
|
58
|
+
return Object.fromEntries(
|
|
59
|
+
WORK_BUDGET_EVENT_KINDS.map((kind) => [kind, 0]),
|
|
60
|
+
) as Record<WorkerWorkBudgetEventKind, number>;
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
export function createWorkerWorkBudgetMeter(input?: {
|
|
64
|
+
nowMs?: () => number;
|
|
65
|
+
}): WorkerWorkBudgetMeter {
|
|
66
|
+
const nowMs = input?.nowMs ?? Date.now;
|
|
67
|
+
let startedAt = nowMs();
|
|
68
|
+
let updatedAt = startedAt;
|
|
69
|
+
let counts = zeroCounters();
|
|
70
|
+
let elapsedMsByKind = zeroCounters();
|
|
71
|
+
|
|
72
|
+
return {
|
|
73
|
+
count(kind, amount = 1) {
|
|
74
|
+
counts[kind] += amount;
|
|
75
|
+
updatedAt = nowMs();
|
|
76
|
+
},
|
|
77
|
+
snapshot() {
|
|
78
|
+
const now = nowMs();
|
|
79
|
+
return {
|
|
80
|
+
startedAt,
|
|
81
|
+
updatedAt,
|
|
82
|
+
elapsedMs: Math.max(0, now - startedAt),
|
|
83
|
+
counts: { ...counts },
|
|
84
|
+
elapsedMsByKind: { ...elapsedMsByKind },
|
|
85
|
+
};
|
|
86
|
+
},
|
|
87
|
+
resetWindow() {
|
|
88
|
+
startedAt = nowMs();
|
|
89
|
+
updatedAt = startedAt;
|
|
90
|
+
counts = zeroCounters();
|
|
91
|
+
elapsedMsByKind = zeroCounters();
|
|
92
|
+
},
|
|
93
|
+
};
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export const WORKER_WORK_BUDGET_YIELD_LIMITS = WORKER_PLATFORM_YIELD_LIMITS;
|
|
97
|
+
|
|
98
|
+
export function shouldYieldWorkerWorkBudget(input: {
|
|
99
|
+
meter: WorkerWorkBudgetMeter;
|
|
100
|
+
limits?: WorkerWorkBudgetYieldLimits;
|
|
101
|
+
projectedSubrequests?: number;
|
|
102
|
+
}): WorkerWorkBudgetYieldDecision {
|
|
103
|
+
const snapshot = input.meter.snapshot();
|
|
104
|
+
const limits = {
|
|
105
|
+
...WORKER_WORK_BUDGET_YIELD_LIMITS,
|
|
106
|
+
...(input.limits ?? {}),
|
|
107
|
+
};
|
|
108
|
+
if (snapshot.elapsedMs >= limits.elapsed) {
|
|
109
|
+
return { shouldYield: true, reason: 'elapsed', snapshot };
|
|
110
|
+
}
|
|
111
|
+
const subrequests =
|
|
112
|
+
snapshot.counts.provider +
|
|
113
|
+
snapshot.counts.receipt +
|
|
114
|
+
snapshot.counts.heartbeat +
|
|
115
|
+
snapshot.counts.sheet +
|
|
116
|
+
snapshot.counts.egress;
|
|
117
|
+
const projectedSubrequests = Math.max(
|
|
118
|
+
0,
|
|
119
|
+
Math.floor(input.projectedSubrequests ?? 0),
|
|
120
|
+
);
|
|
121
|
+
if (subrequests + projectedSubrequests >= limits.subrequest) {
|
|
122
|
+
return { shouldYield: true, reason: 'subrequest', snapshot };
|
|
123
|
+
}
|
|
124
|
+
for (const kind of WORK_BUDGET_EVENT_KINDS) {
|
|
125
|
+
if (snapshot.counts[kind] >= limits[kind]) {
|
|
126
|
+
return { shouldYield: true, reason: kind, snapshot };
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
return { shouldYield: false, reason: null, snapshot };
|
|
130
|
+
}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import type { WorkerWorkBudgetYieldLimits } from './work-budget';
|
|
2
|
+
|
|
3
|
+
// Preview accounts can still enforce the free/internal-service subrequest
|
|
4
|
+
// ceiling even when wrangler config asks for more. This is a planner budget,
|
|
5
|
+
// not the advertised platform ceiling: unbatched tool chunks also spend
|
|
6
|
+
// subrequests on sheet writes, progress/ledger flushes, service-binding hops,
|
|
7
|
+
// and workflow replay around the visible provider calls. Individual log lines
|
|
8
|
+
// are buffered and batched into those flushes; they are not one subrequest each.
|
|
9
|
+
// Keep real headroom here;
|
|
10
|
+
// paid deployments may raise it only once the active account limit is proven by
|
|
11
|
+
// an E2E run, not by config alone.
|
|
12
|
+
export const WORKER_PLATFORM_SUBREQUEST_SOFT_LIMIT = 700;
|
|
13
|
+
|
|
14
|
+
// Fresh unbatched tool calls spend more than the visible provider RPC:
|
|
15
|
+
// durable receipt claim/complete, provider execution, route/service-binding
|
|
16
|
+
// hops, and row progress/sheet writes all share the Worker subrequest pool.
|
|
17
|
+
export const WORKER_PLATFORM_SUBREQUESTS_PER_UNBATCHED_TOOL_CALL = 8;
|
|
18
|
+
|
|
19
|
+
export const WORKER_PLATFORM_YIELD_LIMITS: Required<WorkerWorkBudgetYieldLimits> =
|
|
20
|
+
{
|
|
21
|
+
elapsed: 45_000,
|
|
22
|
+
subrequest: WORKER_PLATFORM_SUBREQUEST_SOFT_LIMIT,
|
|
23
|
+
provider: WORKER_PLATFORM_SUBREQUEST_SOFT_LIMIT,
|
|
24
|
+
tool: 1_500,
|
|
25
|
+
receipt: 2_000,
|
|
26
|
+
heartbeat: 1_000,
|
|
27
|
+
sheet: 800,
|
|
28
|
+
log: 1_500,
|
|
29
|
+
egress: 1_000,
|
|
30
|
+
};
|
|
31
|
+
|
|
32
|
+
export function estimateUnbatchedToolCallSubrequests(input: {
|
|
33
|
+
toolCalls: number;
|
|
34
|
+
}): number {
|
|
35
|
+
return (
|
|
36
|
+
Math.max(0, Math.floor(input.toolCalls)) *
|
|
37
|
+
WORKER_PLATFORM_SUBREQUESTS_PER_UNBATCHED_TOOL_CALL
|
|
38
|
+
);
|
|
39
|
+
}
|
|
@@ -27,6 +27,7 @@ export type WorkflowRetryParamsRef = {
|
|
|
27
27
|
|
|
28
28
|
export type WorkflowRetryPlayParams = {
|
|
29
29
|
runId: string;
|
|
30
|
+
runAttempt?: number;
|
|
30
31
|
playId: string;
|
|
31
32
|
playName: string;
|
|
32
33
|
artifactStorageKey: string;
|
|
@@ -72,6 +73,7 @@ export type WorkflowRetryPlayParams = {
|
|
|
72
73
|
totalRows?: number;
|
|
73
74
|
coordinatorUrl?: string | null;
|
|
74
75
|
coordinatorInternalToken?: string | null;
|
|
76
|
+
runtimeTestFaultHeader?: string | null;
|
|
75
77
|
};
|
|
76
78
|
|
|
77
79
|
export type WorkflowRetryStatePayload<TParams = WorkflowRetryPlayParams> =
|
|
@@ -109,6 +109,41 @@ function resolvePlayRunIntegrationMode(
|
|
|
109
109
|
);
|
|
110
110
|
}
|
|
111
111
|
|
|
112
|
+
function normalizeTestPolicyOverrides(
|
|
113
|
+
value: unknown,
|
|
114
|
+
source: string,
|
|
115
|
+
): Record<string, unknown> {
|
|
116
|
+
if (value && typeof value === 'object' && !Array.isArray(value)) {
|
|
117
|
+
return value as Record<string, unknown>;
|
|
118
|
+
}
|
|
119
|
+
throw new DeeplineError(
|
|
120
|
+
`${source} must be a JSON object.`,
|
|
121
|
+
undefined,
|
|
122
|
+
'INVALID_TEST_POLICY_OVERRIDES',
|
|
123
|
+
);
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function parseEnvTestPolicyOverrides(): Record<string, unknown> | undefined {
|
|
127
|
+
const raw =
|
|
128
|
+
typeof process !== 'undefined'
|
|
129
|
+
? process.env?.DEEPLINE_TEST_POLICY_OVERRIDES
|
|
130
|
+
: undefined;
|
|
131
|
+
const trimmed = raw?.trim();
|
|
132
|
+
if (!trimmed) return undefined;
|
|
133
|
+
let parsed: unknown;
|
|
134
|
+
try {
|
|
135
|
+
parsed = JSON.parse(trimmed);
|
|
136
|
+
} catch (error) {
|
|
137
|
+
const detail = error instanceof Error ? ` ${error.message}` : '';
|
|
138
|
+
throw new DeeplineError(
|
|
139
|
+
`DEEPLINE_TEST_POLICY_OVERRIDES must be valid JSON.${detail}`,
|
|
140
|
+
undefined,
|
|
141
|
+
'INVALID_TEST_POLICY_OVERRIDES',
|
|
142
|
+
);
|
|
143
|
+
}
|
|
144
|
+
return normalizeTestPolicyOverrides(parsed, 'DEEPLINE_TEST_POLICY_OVERRIDES');
|
|
145
|
+
}
|
|
146
|
+
|
|
112
147
|
function sleep(ms: number): Promise<void> {
|
|
113
148
|
return new Promise((resolve) => setTimeout(resolve, ms));
|
|
114
149
|
}
|
|
@@ -202,7 +237,8 @@ function chunkRegisterPlayArtifacts<T>(artifacts: T[]): T[][] {
|
|
|
202
237
|
|
|
203
238
|
type ExecuteToolRawOptions = {
|
|
204
239
|
includeToolMetadata?: boolean;
|
|
205
|
-
responseIntent?: 'raw' | 'row_artifact';
|
|
240
|
+
responseIntent?: 'dataset' | 'raw' | 'row_artifact';
|
|
241
|
+
metadata?: Record<string, unknown>;
|
|
206
242
|
timeout?: number;
|
|
207
243
|
maxRetries?: number;
|
|
208
244
|
};
|
|
@@ -1310,13 +1346,14 @@ export class DeeplineClient {
|
|
|
1310
1346
|
...(options?.includeToolMetadata
|
|
1311
1347
|
? { [INCLUDE_TOOL_METADATA_HEADER]: 'true' }
|
|
1312
1348
|
: {}),
|
|
1313
|
-
|
|
1314
|
-
? { [EXECUTE_RESPONSE_INTENT_HEADER]: options.responseIntent }
|
|
1315
|
-
: {}),
|
|
1349
|
+
[EXECUTE_RESPONSE_INTENT_HEADER]: options?.responseIntent ?? 'raw',
|
|
1316
1350
|
};
|
|
1317
1351
|
return this.http.post<ToolExecution<TData, TMeta>>(
|
|
1318
1352
|
`/api/v2/integrations/${encodeURIComponent(toolId)}/execute`,
|
|
1319
|
-
{
|
|
1353
|
+
{
|
|
1354
|
+
payload: input,
|
|
1355
|
+
...(options?.metadata ? { metadata: options.metadata } : {}),
|
|
1356
|
+
},
|
|
1320
1357
|
headers,
|
|
1321
1358
|
{
|
|
1322
1359
|
forbiddenAsApiError: true,
|
|
@@ -1424,6 +1461,8 @@ export class DeeplineClient {
|
|
|
1424
1461
|
*/
|
|
1425
1462
|
async startPlayRun(request: StartPlayRunRequest): Promise<PlayRunStart> {
|
|
1426
1463
|
const integrationMode = resolvePlayRunIntegrationMode(request);
|
|
1464
|
+
const testPolicyOverrides =
|
|
1465
|
+
request.testPolicyOverrides ?? parseEnvTestPolicyOverrides();
|
|
1427
1466
|
const forceToolRefresh = request.forceToolRefresh === true;
|
|
1428
1467
|
const response = await this.http.post<Record<string, unknown>>(
|
|
1429
1468
|
'/api/v2/plays/run',
|
|
@@ -1468,6 +1507,7 @@ export class DeeplineClient {
|
|
|
1468
1507
|
// different profile pass `request.profile` explicitly.
|
|
1469
1508
|
...(request.profile ? { profile: request.profile } : {}),
|
|
1470
1509
|
...(integrationMode ? { integrationMode } : {}),
|
|
1510
|
+
...(testPolicyOverrides ? { testPolicyOverrides } : {}),
|
|
1471
1511
|
},
|
|
1472
1512
|
);
|
|
1473
1513
|
return normalizePlayRunStart(response);
|
|
@@ -1489,6 +1529,8 @@ export class DeeplineClient {
|
|
|
1489
1529
|
options?: { signal?: AbortSignal },
|
|
1490
1530
|
): AsyncGenerator<PlayLiveEvent> {
|
|
1491
1531
|
const integrationMode = resolvePlayRunIntegrationMode(request);
|
|
1532
|
+
const testPolicyOverrides =
|
|
1533
|
+
request.testPolicyOverrides ?? parseEnvTestPolicyOverrides();
|
|
1492
1534
|
const forceToolRefresh = request.forceToolRefresh === true;
|
|
1493
1535
|
const body = {
|
|
1494
1536
|
...(request.name ? { name: request.name } : {}),
|
|
@@ -1528,6 +1570,7 @@ export class DeeplineClient {
|
|
|
1528
1570
|
: {}),
|
|
1529
1571
|
...(request.profile ? { profile: request.profile } : {}),
|
|
1530
1572
|
...(integrationMode ? { integrationMode } : {}),
|
|
1573
|
+
...(testPolicyOverrides ? { testPolicyOverrides } : {}),
|
|
1531
1574
|
};
|
|
1532
1575
|
for await (const event of this.http.streamSse<PlayLiveEvent>(
|
|
1533
1576
|
'/api/v2/plays/run?stream=true',
|
|
@@ -34,6 +34,7 @@ import {
|
|
|
34
34
|
SYNTHETIC_RUN_HEADER,
|
|
35
35
|
WORKER_CALLBACK_URL_OVERRIDE_HEADER,
|
|
36
36
|
} from '../../shared_libs/play-runtime/coordinator-headers.js';
|
|
37
|
+
import { PLAY_RUNTIME_TEST_FAULT_HEADER } from '../../shared_libs/play-runtime/test-runtime-seams.js';
|
|
37
38
|
|
|
38
39
|
const MAX_DIAGNOSTIC_HEADER_LENGTH = 120;
|
|
39
40
|
const COWORK_NETWORK_HINT =
|
|
@@ -240,16 +241,12 @@ export class HttpClient {
|
|
|
240
241
|
typeof process !== 'undefined'
|
|
241
242
|
? process.env?.DEEPLINE_COORDINATOR_URL
|
|
242
243
|
: undefined;
|
|
244
|
+
const coordinatorInternalToken =
|
|
245
|
+
typeof process !== 'undefined'
|
|
246
|
+
? process.env?.DEEPLINE_INTERNAL_TOKEN
|
|
247
|
+
: undefined;
|
|
243
248
|
if (coordinatorUrl?.trim()) {
|
|
244
249
|
headers[COORDINATOR_URL_OVERRIDE_HEADER] = coordinatorUrl.trim();
|
|
245
|
-
const coordinatorInternalToken =
|
|
246
|
-
typeof process !== 'undefined'
|
|
247
|
-
? process.env?.DEEPLINE_INTERNAL_TOKEN
|
|
248
|
-
: undefined;
|
|
249
|
-
if (coordinatorInternalToken?.trim()) {
|
|
250
|
-
headers[COORDINATOR_INTERNAL_TOKEN_HEADER] =
|
|
251
|
-
coordinatorInternalToken.trim();
|
|
252
|
-
}
|
|
253
250
|
}
|
|
254
251
|
const workerCallbackUrl =
|
|
255
252
|
typeof process !== 'undefined'
|
|
@@ -276,6 +273,24 @@ export class HttpClient {
|
|
|
276
273
|
if (syntheticRun && syntheticRun.trim() && syntheticRun.trim() !== '0') {
|
|
277
274
|
headers[SYNTHETIC_RUN_HEADER] = '1';
|
|
278
275
|
}
|
|
276
|
+
// Test-only runtime seam. The server validates both environment and value
|
|
277
|
+
// before honoring this header.
|
|
278
|
+
const runtimeTestFault =
|
|
279
|
+
typeof process !== 'undefined'
|
|
280
|
+
? process.env?.DEEPLINE_TEST_RUNTIME_FAULT
|
|
281
|
+
: undefined;
|
|
282
|
+
if (runtimeTestFault?.trim()) {
|
|
283
|
+
headers[PLAY_RUNTIME_TEST_FAULT_HEADER] = runtimeTestFault.trim();
|
|
284
|
+
}
|
|
285
|
+
if (
|
|
286
|
+
coordinatorInternalToken?.trim() &&
|
|
287
|
+
(coordinatorUrl?.trim() ||
|
|
288
|
+
workerCallbackUrl?.trim() ||
|
|
289
|
+
runtimeTestFault?.trim())
|
|
290
|
+
) {
|
|
291
|
+
headers[COORDINATOR_INTERNAL_TOKEN_HEADER] =
|
|
292
|
+
coordinatorInternalToken.trim();
|
|
293
|
+
}
|
|
279
294
|
return headers;
|
|
280
295
|
}
|
|
281
296
|
|
|
@@ -74,11 +74,20 @@
|
|
|
74
74
|
*/
|
|
75
75
|
import { DeeplineClient } from './client.js';
|
|
76
76
|
import { DeeplineError } from './errors.js';
|
|
77
|
-
import {
|
|
77
|
+
import {
|
|
78
|
+
attachToolResultListDataset,
|
|
79
|
+
createToolExecuteResult,
|
|
80
|
+
} from '../../shared_libs/play-runtime/tool-result.js';
|
|
78
81
|
export {
|
|
79
82
|
readValue,
|
|
80
83
|
readList,
|
|
81
84
|
} from '../../shared_libs/play-runtime/tool-result.js';
|
|
85
|
+
import { createDeferredPlayDataset } from '../../shared_libs/plays/dataset.js';
|
|
86
|
+
import {
|
|
87
|
+
QUERY_RESULT_DATASET_PAGE_SIZE,
|
|
88
|
+
isCustomerDbDatasetTool,
|
|
89
|
+
isQueryResultDatasetTool,
|
|
90
|
+
} from '../../shared_libs/play-runtime/query-result-dataset.js';
|
|
82
91
|
import type {
|
|
83
92
|
PlayDataset,
|
|
84
93
|
PlayDatasetInput,
|
|
@@ -1473,8 +1482,12 @@ export class DeeplineContext {
|
|
|
1473
1482
|
): Promise<ToolExecuteResult> => {
|
|
1474
1483
|
const response = await this.client.executeTool(toolId, input, {
|
|
1475
1484
|
includeToolMetadata: true,
|
|
1485
|
+
responseIntent: 'dataset',
|
|
1486
|
+
});
|
|
1487
|
+
return toolExecutionEnvelopeToResult(toolId, response, {
|
|
1488
|
+
client: this.client,
|
|
1489
|
+
input,
|
|
1476
1490
|
});
|
|
1477
|
-
return toolExecutionEnvelopeToResult(toolId, response);
|
|
1478
1491
|
},
|
|
1479
1492
|
};
|
|
1480
1493
|
}
|
|
@@ -1724,9 +1737,163 @@ function extractorDescriptorRecord(
|
|
|
1724
1737
|
);
|
|
1725
1738
|
}
|
|
1726
1739
|
|
|
1740
|
+
function rowsFromUnknown(value: unknown): Record<string, unknown>[] {
|
|
1741
|
+
if (!Array.isArray(value)) return [];
|
|
1742
|
+
return value.map((row) => (isRecord(row) ? row : { value: row }));
|
|
1743
|
+
}
|
|
1744
|
+
|
|
1745
|
+
function finiteNonNegativeInteger(value: unknown): number | null {
|
|
1746
|
+
if (typeof value !== 'number' || !Number.isFinite(value) || value < 0) {
|
|
1747
|
+
return null;
|
|
1748
|
+
}
|
|
1749
|
+
return Math.floor(value);
|
|
1750
|
+
}
|
|
1751
|
+
|
|
1752
|
+
function finitePositiveInteger(value: unknown): number | null {
|
|
1753
|
+
const integer = finiteNonNegativeInteger(value);
|
|
1754
|
+
return integer !== null && integer > 0 ? integer : null;
|
|
1755
|
+
}
|
|
1756
|
+
|
|
1757
|
+
function stableHash(value: string): string {
|
|
1758
|
+
let hash = 0;
|
|
1759
|
+
for (let index = 0; index < value.length; index += 1) {
|
|
1760
|
+
hash = Math.imul(31, hash) + value.charCodeAt(index);
|
|
1761
|
+
hash |= 0;
|
|
1762
|
+
}
|
|
1763
|
+
return Math.abs(hash).toString(36);
|
|
1764
|
+
}
|
|
1765
|
+
|
|
1766
|
+
function attachSdkQueryResultDatasetResult(
|
|
1767
|
+
toolId: string,
|
|
1768
|
+
result: ToolExecuteResult,
|
|
1769
|
+
options?: {
|
|
1770
|
+
client: DeeplineClient;
|
|
1771
|
+
input: Record<string, unknown>;
|
|
1772
|
+
},
|
|
1773
|
+
): ToolExecuteResult {
|
|
1774
|
+
if (!options || !isQueryResultDatasetTool(toolId)) return result;
|
|
1775
|
+
const raw = isRecord(result.toolResponse.raw)
|
|
1776
|
+
? result.toolResponse.raw
|
|
1777
|
+
: null;
|
|
1778
|
+
const dataset = isRecord(raw?.dataset) ? raw.dataset : null;
|
|
1779
|
+
const totalRows = finiteNonNegativeInteger(dataset?.total_rows);
|
|
1780
|
+
const sql =
|
|
1781
|
+
typeof options.input.sql === 'string'
|
|
1782
|
+
? options.input.sql
|
|
1783
|
+
: typeof options.input.query === 'string'
|
|
1784
|
+
? options.input.query
|
|
1785
|
+
: typeof raw?.sql === 'string'
|
|
1786
|
+
? raw.sql
|
|
1787
|
+
: null;
|
|
1788
|
+
if (!dataset || totalRows === null || !sql) {
|
|
1789
|
+
return result;
|
|
1790
|
+
}
|
|
1791
|
+
const datasetLimit =
|
|
1792
|
+
finitePositiveInteger(dataset.returned_limit) ?? totalRows;
|
|
1793
|
+
const previewRows = rowsFromUnknown(raw?.rows).slice(0, 25);
|
|
1794
|
+
const fetchPage = async (
|
|
1795
|
+
offset: number,
|
|
1796
|
+
limit: number,
|
|
1797
|
+
): Promise<Record<string, unknown>[]> => {
|
|
1798
|
+
if (limit <= 0 || offset >= totalRows) return [];
|
|
1799
|
+
const response = await options.client.executeTool(toolId, options.input, {
|
|
1800
|
+
includeToolMetadata: true,
|
|
1801
|
+
responseIntent: 'dataset',
|
|
1802
|
+
metadata: {
|
|
1803
|
+
query_result_dataset: {
|
|
1804
|
+
limit: datasetLimit,
|
|
1805
|
+
offset,
|
|
1806
|
+
page_size: Math.min(limit, QUERY_RESULT_DATASET_PAGE_SIZE),
|
|
1807
|
+
total_rows: totalRows,
|
|
1808
|
+
},
|
|
1809
|
+
...(isCustomerDbDatasetTool(toolId)
|
|
1810
|
+
? {
|
|
1811
|
+
customer_db_dataset: {
|
|
1812
|
+
limit: datasetLimit,
|
|
1813
|
+
offset,
|
|
1814
|
+
page_size: Math.min(limit, QUERY_RESULT_DATASET_PAGE_SIZE),
|
|
1815
|
+
total_rows: totalRows,
|
|
1816
|
+
},
|
|
1817
|
+
}
|
|
1818
|
+
: {}),
|
|
1819
|
+
},
|
|
1820
|
+
});
|
|
1821
|
+
const pageRaw = isRecord(response.toolResponse?.raw)
|
|
1822
|
+
? response.toolResponse.raw
|
|
1823
|
+
: null;
|
|
1824
|
+
return rowsFromUnknown(pageRaw?.rows);
|
|
1825
|
+
};
|
|
1826
|
+
const collectRows = async (
|
|
1827
|
+
limit: number | undefined,
|
|
1828
|
+
): Promise<Record<string, unknown>[]> => {
|
|
1829
|
+
const target = Math.min(limit ?? totalRows, totalRows, datasetLimit);
|
|
1830
|
+
const collected: Record<string, unknown>[] = [];
|
|
1831
|
+
for (
|
|
1832
|
+
let offset = 0;
|
|
1833
|
+
offset < target;
|
|
1834
|
+
offset += QUERY_RESULT_DATASET_PAGE_SIZE
|
|
1835
|
+
) {
|
|
1836
|
+
collected.push(
|
|
1837
|
+
...(await fetchPage(
|
|
1838
|
+
offset,
|
|
1839
|
+
Math.min(QUERY_RESULT_DATASET_PAGE_SIZE, target - offset),
|
|
1840
|
+
)),
|
|
1841
|
+
);
|
|
1842
|
+
}
|
|
1843
|
+
return collected.slice(0, target);
|
|
1844
|
+
};
|
|
1845
|
+
const executionNonce =
|
|
1846
|
+
typeof result.job_id === 'string' && result.job_id.trim()
|
|
1847
|
+
? result.job_id.trim()
|
|
1848
|
+
: `${Date.now().toString(36)}-${Math.random().toString(36).slice(2)}`;
|
|
1849
|
+
const safeNonce = executionNonce
|
|
1850
|
+
.replace(/[^A-Za-z0-9_.:-]/g, '_')
|
|
1851
|
+
.slice(0, 64);
|
|
1852
|
+
const datasetScope = `${options.client.baseUrl}:${toolId}:${sql}:${datasetLimit}`;
|
|
1853
|
+
const playDataset = createDeferredPlayDataset({
|
|
1854
|
+
datasetKind: 'csv',
|
|
1855
|
+
datasetId: `sdk-tool-list:${toolId}:${stableHash(datasetScope)}:${datasetLimit}:${safeNonce}`,
|
|
1856
|
+
count: Math.min(totalRows, datasetLimit),
|
|
1857
|
+
previewRows,
|
|
1858
|
+
sourceLabel: 'query result rows',
|
|
1859
|
+
tableNamespace: null,
|
|
1860
|
+
resolvers: {
|
|
1861
|
+
count: async () => Math.min(totalRows, datasetLimit),
|
|
1862
|
+
peek: async (limit) => collectRows(limit),
|
|
1863
|
+
materialize: async (limit) => collectRows(limit),
|
|
1864
|
+
iterate: () =>
|
|
1865
|
+
({
|
|
1866
|
+
async *[Symbol.asyncIterator]() {
|
|
1867
|
+
const count = Math.min(totalRows, datasetLimit);
|
|
1868
|
+
for (
|
|
1869
|
+
let offset = 0;
|
|
1870
|
+
offset < count;
|
|
1871
|
+
offset += QUERY_RESULT_DATASET_PAGE_SIZE
|
|
1872
|
+
) {
|
|
1873
|
+
yield* await fetchPage(
|
|
1874
|
+
offset,
|
|
1875
|
+
Math.min(QUERY_RESULT_DATASET_PAGE_SIZE, count - offset),
|
|
1876
|
+
);
|
|
1877
|
+
}
|
|
1878
|
+
},
|
|
1879
|
+
}) as AsyncIterable<Record<string, unknown>>,
|
|
1880
|
+
},
|
|
1881
|
+
});
|
|
1882
|
+
return attachToolResultListDataset(result, {
|
|
1883
|
+
name: 'rows',
|
|
1884
|
+
path: 'toolResponse.raw.rows',
|
|
1885
|
+
dataset: playDataset,
|
|
1886
|
+
count: Math.min(totalRows, datasetLimit),
|
|
1887
|
+
});
|
|
1888
|
+
}
|
|
1889
|
+
|
|
1727
1890
|
function toolExecutionEnvelopeToResult(
|
|
1728
1891
|
fallbackToolId: string,
|
|
1729
1892
|
response: ToolExecution,
|
|
1893
|
+
options?: {
|
|
1894
|
+
client: DeeplineClient;
|
|
1895
|
+
input: Record<string, unknown>;
|
|
1896
|
+
},
|
|
1730
1897
|
): ToolExecuteResult {
|
|
1731
1898
|
const raw = response.toolResponse?.raw ?? null;
|
|
1732
1899
|
const meta = response.toolResponse?.meta;
|
|
@@ -1735,30 +1902,37 @@ function toolExecutionEnvelopeToResult(
|
|
|
1735
1902
|
: null;
|
|
1736
1903
|
const toolMetadata = isRecord(metadata) ? metadata : {};
|
|
1737
1904
|
|
|
1738
|
-
return
|
|
1739
|
-
|
|
1740
|
-
|
|
1741
|
-
|
|
1742
|
-
|
|
1743
|
-
|
|
1744
|
-
|
|
1745
|
-
|
|
1746
|
-
|
|
1747
|
-
|
|
1748
|
-
|
|
1749
|
-
|
|
1750
|
-
|
|
1751
|
-
|
|
1752
|
-
|
|
1753
|
-
|
|
1754
|
-
|
|
1755
|
-
|
|
1756
|
-
|
|
1757
|
-
|
|
1758
|
-
|
|
1759
|
-
|
|
1760
|
-
|
|
1761
|
-
|
|
1905
|
+
return attachSdkQueryResultDatasetResult(
|
|
1906
|
+
fallbackToolId,
|
|
1907
|
+
createToolExecuteResult({
|
|
1908
|
+
status:
|
|
1909
|
+
typeof response.status === 'string' ? response.status : 'completed',
|
|
1910
|
+
jobId: typeof response.job_id === 'string' ? response.job_id : undefined,
|
|
1911
|
+
result: {
|
|
1912
|
+
data: raw,
|
|
1913
|
+
...(isRecord(meta) ? { meta } : {}),
|
|
1914
|
+
},
|
|
1915
|
+
metadata: {
|
|
1916
|
+
toolId:
|
|
1917
|
+
typeof toolMetadata.toolId === 'string'
|
|
1918
|
+
? toolMetadata.toolId
|
|
1919
|
+
: fallbackToolId,
|
|
1920
|
+
extractors: extractorDescriptorRecord(toolMetadata.extractors),
|
|
1921
|
+
targetGetters: stringArrayRecord(toolMetadata.targetGetters),
|
|
1922
|
+
listExtractorPaths: stringArray(toolMetadata.listExtractorPaths),
|
|
1923
|
+
listIdentityGetters: stringArrayRecord(
|
|
1924
|
+
toolMetadata.listIdentityGetters,
|
|
1925
|
+
),
|
|
1926
|
+
},
|
|
1927
|
+
execution: {
|
|
1928
|
+
idempotent: true,
|
|
1929
|
+
cached: false,
|
|
1930
|
+
source: 'live',
|
|
1931
|
+
},
|
|
1932
|
+
meta: isRecord(response.meta) ? response.meta : undefined,
|
|
1933
|
+
}),
|
|
1934
|
+
options,
|
|
1935
|
+
);
|
|
1762
1936
|
}
|
|
1763
1937
|
|
|
1764
1938
|
export function defineInput<TInput>(
|